• Является ли такой способ выделения массива объектов на хипе идиоматичным?

    @code_panik
    "Если требуется работа с голой памятью", - в c++ обычно не работают с голой памятью (free store). Работают с объектами, у которых есть время жизни. Мы выделяем память посредством низкоуровневых функций, только чтобы потом в ней создавать живые объекты, например с помощью placement new.

    В этом смысле подход "без использования векторов и прочего" не имеет совершенно никакого значения. Как руками мы выделяем память и создаем в ней объекты, так поступает и вектор. Но только вектор позволяет нам сохранить здоровыми несколько клеток головного мозга. Другие аргументы в соответствующем разделе super-faq по ключевому слову vector.

    "например, какое-то апи требует тип T *" - vector<T>::data().

    Идиоматичность кода подразумевает использование актуальных стандартных средств языка. Оба примера напоминают c++03. Если код плохо читаем как в первом примере, то имеет смысл поискать лучшие альтернативы. Если их нет, то завернуть код во что-нибудь удобочитаемое, вектор например. Про идиоматичную низкоуровневую работу с памятью можно почитать у Страуструпа в примере реализации вектора.

    Вопрос оптимизации - отдельная тема. Оптимизация под конкретные нужды не обязана быть идиоматичной или в принципе соответствовать общим представлениям. Например, вместо хранения массива структур, мы можем создать и использовать отдельно массивы полей структур. Можете поискать информацию по data-oriented design.
    Ответ написан
    Комментировать
  • Как превратить void() в void (**)()?

    @code_panik
    UPD: Похоже, проблема в библиотеке
    https://github.com/espressif/arduino-esp32/issues/7675
    Исправляющий комит
    https://github.com/espressif/arduino-esp32/commit/...

    Довольно странное требование для api.
    Если вы пишите класс-обертку над этой функцией, то она принимает просто функцию, а не указатель на указатель.
    В C++ функция определяется своим адресом, и правила передачи функции в функцию похожи на правила передачи обычного массива.
    Похоже, проблема именно в объявлении вашего register_callback, которое можно реализовать как в этом примере
    #include <iostream>
    using namespace std;
    
    using esp_spp_cb_event_t = int;
    using esp_spp_cb_param_t = void;
    
    void btCallback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
        cout << "btCallback" << endl;
    }
    
    class MyBt {        
    public:
        typedef void (*callback_type)(esp_spp_cb_event_t, esp_spp_cb_param_t *);    
    
        void register_callback(callback_type cb) {
            cb(0, nullptr);
        }
    };
    
    int main()
    {    
        MyBt bt;
        bt.register_callback(btCallback);
        return 0;
    }
    Ответ написан
    5 комментариев
  • Как в деструкторе базового класса вызвать переопределённый метод?

    @code_panik
    Порядок вызова деструкторов определяется стандартом языка. Сначала выполняется деструктор дочернего класса, затем деструкторы членов дочернего класса и только потом родительские деструкторы (https://isocpp.org/wiki/faq/dtors#order-dtors-for-...). Поэтому доступные данные и методы в родительском деструкторе ограничены родительским подобъектом и его родителями.
    Родительский деструктор можно сделать тривиальным ~Parent() = default (ничего не делать) или чистым виртуальным virtual ~Parent() = 0 (не определен). Только во втором случае невозможно создавать объекты типа "Parent".
    Ответ написан
    Комментировать
  • Почему msvc оптимизирует конструкторы несмотря на флаги?

    @code_panik
    Разберемся, в каком порядке выводятся сообщения на экран во втором случае gnu++11.
    Сначала в main вызываются два конструктора по умолчанию: для imnotdumb1 и временного объекта dumb_array();.
    Далее в main вызывается метод dumb_array & operator=( dumb_array temp), в котором temp инициализируется посредством метода dumb_array (dumb_array&& other), который перед выводом на экран сообщения operator=( dumb_array tmp) вызывает ещё один конструктор по умолчанию.
    Итого 3 конструктора по умолчанию, один перемещения, один оператор присваивания.

    MSVC вызывает первые два конструктора по умолчанию и применяет move elision к инициализации temp.
    То есть весь пример сводится к такому коду
    #include <iostream>
    using namespace std;
    
    struct Foo {
    	Foo() { cout << "default ctor\n"; }
    	Foo(Foo&& arg) { cout << "move ctor\n"; }
    };
    
    void foo(Foo arg) {
    	cout << "foo\n";
    }
    
    int main () {    
    	foo(Foo());
    	return 0;
    }


    В c++ существуют обязательные (mandatory) и необязательные случаи copy/move elision, https://en.cppreference.com/w/cpp/language/copy_elision.
    Применение правила из примера
    In the initialization of an object, when the source object is a nameless temporary and is of the same class type (ignoring cv-qualification) as the target object.

    до c++17 было необязательным к реализации, на усмотрение разработчиков компилятора.
    В Microsoft считают такое правило обязательным вне зависимости от флагов компиляции,
    Mandatory copy/move elision in Visual Studio

    The C++ standard requires copy or move elision when the returned value is initialized as part of the return statement (such as when a function with return type Foo returns return Foo()). The Microsoft Visual C++ compiler always performs copy and move elision for return statements where it is required to do so, regardless of the flags passed to the compiler. This behavior is unchanged.


    До c++17 требовалось, чтобы соответствующий copy/move ctor в случае copy elision всё равно был доступен (cppreference, там же):
    This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed
    Ответ написан
    1 комментарий
  • Почему нет доступа к приватному атрибуту?

    @code_panik
    Пожалуйста, приводите в вопросах содержимое файлов с кодом полностью. Без полных листингов нельзя точно сказать, в чем проблема.
    Обратите внимание, что для вызова operator+(Car& c1, Car& c2) функция GetCar должна возвращать Car&, а не Car или const Car& (только не возвращайте ссылку на переменную, объявленную внутри функции).
    Замените везде operator+(Car& c1, Car& c2) на operator+(const Car& c1, const Car& c2).
    Работающий пример.
    И замените, по возможности, компилятор на более современный.
    Ответ написан
  • Как убрать ошибки взаимодействия между классами?

    @code_panik
    Если структуру классов не нужно менять, то можно сделать как в рабочем примере.
    Перед Window_mgr достаточно объявить (forward declare) class Screen. Просто нужно иметь ввиду, что в файле класса Window_mgr это неполный тип (incomplete type).
    И во friend объявлении не хватает Window_mgr::ScreenIndex.
    Ответ написан
    1 комментарий
  • Как это можно реализовать?

    @code_panik
    Если enum не видно извне как деталь реализации, то можно так.
    #include <iostream>
    using namespace std;
    
    class BaseTransport {    
    public:
        virtual void Move() {        
            onArrival();            
        }
    private:
        virtual void onArrival() = 0;
    };
    
    class GroundTransport : public BaseTransport {
    private:
        void onArrival() override {
            cout << __PRETTY_FUNCTION__ << endl;
            state = GroundEnum::Finished;
        }
        enum class GroundEnum { Finished };    
        GroundEnum state;
    };
    
    class SeaTransport : public BaseTransport {    
    private:
        void onArrival() override {
            cout << __PRETTY_FUNCTION__ << endl;
            state = SeaEnum::ReturnToBegin;
        }
        enum class SeaEnum { ReturnToBegin };
        SeaEnum state;
    };
    
    int main() {
        BaseTransport* st = new SeaTransport();
        st->Move();    
        BaseTransport* gt = new GroundTransport();
        gt->Move();
        return 0;
    }
    Ответ написан
    Комментировать
  • Почему при определении шаблона функции, принадлежащего шаблону класса, не нужно указывать шаблонный тип?

    @code_panik
    // non-type partial specialization 'func' is not allowed

    Буквально значит, что язык позволяет определить частичную специализацию только для пользовательского типа. Частичная специализация функции запрещена.
    Общий вид частичной специализации можно посмотреть здесь. Видно, что в определениях есть непустые угловые скобки после template и после имени, как и в примере из вопроса.
    Значит, скобки после имени функции в определении не нужны, если это не полная специализация. И в полной специализации скобки опускают, если тип выводится из типов аргументов (пример).
    Ответ написан