Ответы пользователя по тегу C++
  • Почему нельзя применять инкремент к имени массива?

    @Mace_UA
    Потому что это бессмысленно. Как и нельзя применить инкремент к объекту типа std::vector. Неважно, где выделена память -- важно, к какому объекту вы пытаетесь применить эту операцию.
    В случае с массивами инкремент не имеет смысла, т.к. неясно, что должно произойти с самим массивом после этого (ведь инкремент -- операция, модифицирующая свой аргумент, в отличие от, например, оператора +).
    Инкрементить можно итератор/указатель на какой-то элемент этого массива, чтобы получить итератор/указатель на следующий элемент.

    Важно понимать, что массивы не являются указателями. Это принципиально разные сущности.
    Массив содержит в себе все свои элементы и охватывает весь диапазон (нельзя заставить массив уже после создания начать охватывать только часть своего диапазона).
    Указатель же только ссылается на память, принадлежащую кому-то другому (возможно, какому-нибудь массиву).

    Хотя массивы и умеют неявно приводиться к указателям, и во многих случаях это делают автоматически.
    Например, при применении оператора + к массиву.

    Оператор + (в общем случае) не изменяет своих операндов, а создаёт новый объект, поэтому с ним такой неоднозначности не будет.
    В выражении arr+1 (где arr -- массив) компилятор сначала как бы попытается применить оператор + к массиву и целому числу, у него не получится (т.к. к массиву самому по себе что-то прибавлять нельзя -- опять же, это бессмысленная/неоднозначная операция). Но затем он увидит, что у этого массива есть оператор неявной конвертации к чему-то, что можно использовать в таком контексте: указателю. И поэтому воспользуется данным приведением, сконвертирует arr к новому указателю, и применит +1 уже к этому указателю, в результате вернув указатель на следующий элемент.
    Принципиальное отличие в том, что в данном примере сам массив никто не пытается модифицировать.

    Вы можете спросить: почему тогда в случае с ++arr компилятор не провернёт ту же фишку, приведя массив к указателю и применив инкремент к этому указателю?
    Фишка в том, что сам массив от этого не изменится (т.к. менялся бы только временный объект-указатель), и в результате вы бы не получили ничего. Вот совсем ничего. А это вряд ли то, что вы ожидаете. Потому что этот временный проинкрементированный указатель после завершения операции был бы тут же уничтожен -- на оригинальном массиве это бы никак не сказалось. Операция оказалась полностью бессмысленной.

    Поэтому для встроенных типов такие операции над prvalue (временными безымянными объектами, не привязанными к переменной) запрещены. Здесь уже речь не идёт конкретно о массивах.
    Это то же самое, что написать ++(1+2). Не скомпилится. Так как выражение (1+2) вернёт временный объект типа int, не привязанный ни к какой именованной переменной, и его инкрементировать попросту бессмысленно.

    Так что если вы хотите использовать инкремент с массивом, предполагая, что эта операция должна осуществлять переход к следующему элементу -- просто создайте именованную переменную-указатель через описанное выше неявное приведение, и инкрементируйте уже этот указатель.
    В таком случае всё будет в порядке, поскольку 1) данный указатель будет новым объектом, и его изменение само по себе никак не повлияет на сам массив; 2) инкремент указателя -- операция понятная и однозначная:

    int arr[42];
    //++arr; // error!
    int* ptr = arr; // array-to-pointer decay
    ++ptr; // ok!


    То же самое можно сказать и о динамических массивах, не являющихся указателями. В C++ их роль чаще всего играют экземпляры шаблона std::vector:

    std::vector<int> arr(42);
    //++arr; // error!
    int* ptr = arr.data();
    ++ptr; // ok!
    Ответ написан
    Комментировать
  • С или modernC++ для IoT?

    @Mace_UA
    Не думаю, что оверхед от эксепшенов и shared_ptr'ов в C++ намного больше, чем оверхед от аналогичных вещей в Си. Ведь "эксепшены" там можно частично имитировать через setjmp/longjmp и прочие прелести, а "shared_ptr" навелосипедить ручками, сделав структуру для хранения данных, указателя на функцию-делитер и атомарных счётчиков strong и weak ссылок, работа с которыми будет осуществляться через соответствующие функции из <stdatomic.h>.
    Ну и "weak_ptr" заодно заимплементить по аналогии с плюсовым, для полноты картины.

    К чему я это всё?

    Если Вам не нужен определённый функционал -- просто не используйте его.
    И это утверждение справедливо вне зависимости от выбранного языка.
    Если Вам не нужна некоторая часть языка -- это не причина отказываться от него полностью. Весь C++ на сто процентов, по-моему, не нужен вообще ни на одном проекте :)

    А из фишек modern C++ в эмбеддеде наибольшую пользу, скорее всего, принесёт constexpr. В сочетании с шаблонами поможет вынести в компайл-тайм много всего -- причём, в отличие от рекурсивной функциональщины, использующей только шаблоны, это будет смотреться читабельно. В C++11 constexpr слабоват, но начиная с C++14 с ним есть где разогнаться.

    Из структур данных в первую очередь посмотрите на array и tuple.

    Для безопасности могут помочь enum class и nullptr.

    Если в Вашем коде имеют место полиморфные иерархии, то override сделает код понятнее и предотвратит неприятные ошибки с hiding'ом вместо переопределения.

    Рекомендую посмотреть на ютубе лекции Jason'а Turner'а -- у него были видосики о применении современного C++ в эмбеддеде. Он показывал сгенерированный ассемблер -- отличий от аналогичного низкоуровневого сишного кода вообще не было. Зато в исходном коде типобезопасность и расширяемость, которых добиться одной чистой Сишкой не всегда возможно.

    Думаю, с выходом новых стандартов C++ и появлением их поддержки в компиляторах этот язык должен набрать большую популярность в эмбеддеде.
    Ответ написан
    Комментировать
  • Как отсортировать элементы так, чтобы слева находились нечетные элементы, а справа — четные?

    @Mace_UA
    2017 год. Вам не нужны std::binary_function и прочая устаревшая фигня. Да и можно заюзать лямбду, если этот компаратор нужен только в одном месте кода.

    Судя по описанию задачи, Вам нужно не отсортировать массив -- Вам нужно сделать partition.

    Воспользуйтесь стандартными алгоритмами std::partition (если относительный порядок чётных и нечётных элементов между собой не важен) или std::stable_partition (если нужно сохранить оригинальный порядок).

    Пример:
    std::stable_partition(vec.begin(), vec.end(), [](int x) { return x % 2 == 1; });
    Ответ написан
    Комментировать
  • C++11, C++14. Использование auto - признак дурного тона?

    @Mace_UA
    Нет, это не признак дурного тона. Auto -- замечательная фича, позволяющая сделать код более читабельным. Но, как и практически любая хорошая фича C++, она может сделать вам только хуже, если применять её без надобности там, где она не нужна. Поэтому поддерживаю уже высказанное здесь мнение
    если вы без указания типа и так УЖЕ ИМЕЕТЕ достаточно информации, чтобы уверенно работать со значением - тогда можно использовать auto. Если чувствуете, что не имеете, или сомневаетесь, что имеете - лучше тип указать.


    При объявлениях функций auto в качестве типа возвращаемого значения вы так просто не напишете. Это должны быть инлайн функции. Как правило, такая потребность может встретиться разве что при написании шаблонов, которые реализуются прямо в хедере и для методов в которых порой вывести тип возвращаемого значения без auto крайне тяжело. Если вы школьник и не так давно программируете, на ближайшее время вообще забудьте про такую возможность. Она вам не нужна. А для разработчиков всяких хитроумных шаблонных библиотек это может быть очень даже полезной фичей.

    И ещё один аргумент за использование auto при определении переменных. Большинство современных IDE нормально справляются с тем, чтобы сообщить вам тип переменной, когда вы просто наводите мышкой на её имя, даже если она объявлена как auto. Это во многих случаях смягчает негативные стороны использования auto, если таковые вообще имели место в данном куске кода.

    А для более "продвинутых" программистов рекомендую к прочтению вот эту статью Герба Саттера. Всё прекрасно разложил по полочкам насчёт использования auto и объяснил, почему это в большинстве случаев не так страшно, как некоторым кажется на первый взгляд. Конечно, предложенная в конце идиома "almost always auto" спорная, C++ это всё-таки не джаваскрипт. Но остальное написано очень разумно.
    Ответ написан
    Комментировать
  • Как создать через макросы уникальный префикс к генерируемому имени типа на этапе компиляции?

    @Mace_UA
    __FILE__ юзать не нужно, достаточно помещать классы в анонимные пространства имён. Такие классы будут уникальными для каждой единицы компиляции. Тогда будет достаточно просто прикрутить __LINE__ к идентификатору объекта класса. А сам класс, увы, придётся определять отдельным макросом, раз нужна возможность создавать по несколько объектов.

    Вот небольшой пример. Проект из трёх файлов.
    Приём с конкатенацией числа и идентификатора с помощью макросов взят отсюда:
    stackoverflow.com/questions/1597007/creating-c-mac...

    "header.hpp":
    #ifndef HEADER_HPP_INCLUDED
    #define HEADER_HPP_INCLUDED
    
    #include <iostream>
    
    #define TOKENPASTE(x, y) x ## y
    #define TOKENPASTE2(x, y) TOKENPASTE(x, y)
    
    #define CLASS_MAGIC                                                           \
        namespace                                                                 \
        {                                                                         \
            struct FixedNameClass                                                 \
            {                                                                     \
                explicit FixedNameClass(int line)                                 \
                {                                                                 \
                    std::cout << "Hello from \"" << __FILE__ <<                   \
                                 "\", line " << line << std::endl;                \
                }                                                                 \
            };                                                                    \
        }
    
    #define OBJECT_MAGIC                                                          \
        namespace                                                                 \
        {                                                                         \
            FixedNameClass TOKENPASTE2(variable_name_object, __LINE__)(__LINE__); \
        }
    
    #endif // HEADER_HPP_INCLUDED


    "main.cpp":
    #include "header.hpp"
    
    CLASS_MAGIC
    OBJECT_MAGIC
    OBJECT_MAGIC
    OBJECT_MAGIC
    
    int main()
    {
    }
    
    OBJECT_MAGIC
    OBJECT_MAGIC


    "other.cpp":
    #include "header.hpp"
    
    CLASS_MAGIC
    OBJECT_MAGIC
    OBJECT_MAGIC


    Вывод:
    Hello from "other.cpp", line 4
    Hello from "other.cpp", line 5
    Hello from "main.cpp", line 4
    Hello from "main.cpp", line 5
    Hello from "main.cpp", line 6
    Hello from "main.cpp", line 12
    Hello from "main.cpp", line 13
    Ответ написан
    Комментировать