С++ 11 виртуальных деструкторов и автоматическая генерация специальных функций перемещения

Правила автоматического создания специальных функций перемещения (оператор конструктора и присваивания) в С++ 11 указывают, что деструктор не может быть объявлен. Логика, по-видимому, состоит в том, что если вам нужно сделать что-то особенное в уничтожении, это может не быть безопасным.

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

Я предполагаю, что даже пустой деструктор не позволит компилятору автоматически генерировать специальные функции перемещения. Как в:

class Base {
    virtual ~Base() { }
};

Однако вы можете установить деструктор по умолчанию:

class Base {
    virtual ~Base() = default;
}

Итак, вопрос 1: позволит ли это компилятору автоматически генерировать специальные функции перемещения?

Однако существует проблема с явным деструктором по умолчанию. По крайней мере, в случае GCC 4.8.2 подпись неявно изменяется на noexcept. Как в:

class Base {
    virtual ~Base() = default; // compiler changes to:
    // virtual ~Base() noexcept;
}

Пока у меня нет проблем с noexcept в деструкторе, это сломает следующий "клиентский" код:

class Sub : public Base {
    virtual ~Sub(); // this declaration is now "looser" because of no noexcept
}

Итак, вопрос 2 более подходит: есть ли способ разрешить автоматическую генерацию специальных функций перемещения в С++ 11 и обеспечить правильную цепочку деструкторов для подклассов (как описано выше), все без нарушения подкласса ( "клиент" ")?

Ответ 1

  • Нет, деструктор по умолчанию все еще считается определяемым пользователем, поэтому он предотвратит создание операций перемещения. Также объявите операции перемещения default -ed, чтобы компилятор сгенерировал их.

  • Вам нужно только объявить операции перемещения как default -ed в базовом классе. В производном классе деструктор больше не будет определяться пользователем (если вы не указали это явно), поэтому операции перемещения не будут удалены.

Итак, что бы я сделал, это следующее:

class Base
{
    virtual ~Base() = default;
    Base(Base&&) = default;
    Base& operator=(Base&&) = default;
    // probably need to think about copy operations also, as the move disables them
    Base(const Base&) = default;
    Base& operator=(const Base&) = default;
};

Я очень рекомендую этот разговор человеком, который внес наиболее вероятно семантику перемещения: http://www.slideshare.net/ripplelabs/howard-hinnant-accu2014

Или, если вы можете взять себя в руки, вы должны читать Пункт 17: Понять специальную функцию функции члена из превосходной книги Скотта Майерса "Эффективный современный С++". Эта проблема отлично объясняется.

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

PSS: Я думаю, что по умолчанию деструкторы помечены как noexcept в С++ 11/14, поэтому явно не указывая на это, не должно быть никаких проблем:

Наследование конструкторов и неявно объявленные значения по умолчанию конструкторы, конструкторы копирования, конструкторы перемещения, деструкторы, операторы присваивания копий, операторы переадресации - все noexcept (true) по умолчанию, если только они не требуются для вызова функции это noexcept (false), и в этом случае эти функции noexcept (ложь).