Подтвердить что ты не робот

Виртуальный деструктор по умолчанию предотвращает операции сгенерированных компилятором операций?

Вдохновленный сообщением Почему деструктор отключает генерацию неявных методов перемещения?, мне было интересно, верно ли это для виртуального деструктора по умолчанию, например

class WidgetBase // Base class of all widgets
{
    public:
        virtual ~WidgetBase() = default;
        // ...
};

Поскольку класс предназначен для базового класса иерархии виджета, я должен определить его виртуальный деструктор, чтобы избежать утечек памяти и поведения undefined при работе с указателями базового класса. С другой стороны, я не хочу, чтобы компилятор автоматически генерировал операции перемещения.

Предоставляет ли виртуальный деструктор по умолчанию предотвращение операций перемещения сгенерированных компилятором?

4b9b3361

Ответ 1

Да, объявление любого деструктора предотвратит неявное объявление конструктора перемещения.

N3337 [class.copy]/9: Если определение класса X явно не объявляет конструктор перемещения, оно будет объявлено неявно как по умолчанию, если и только если

  • X не имеет объявленного пользователем конструктора копирования,
  • X не имеет объявленного пользователем оператора назначения копирования,
  • X не имеет объявленного пользователем оператора назначения перемещения,
  • X не имеет объявленного пользователем деструктора и
  • конструктор перемещения не будет неявно определен как удаленный.

Объявление деструктора и определение его как default считается объявленным пользователем.

Вам нужно объявить конструктор перемещения и определить его как default самостоятельно:

WidgetBase(WidgetBase&&) = default;

Обратите внимание, что это, в свою очередь, определяет конструктор копирования как delete, поэтому вам нужно также default:

WidgetBase(const WidgetBase&) = default;

Правила для операторов присваивания копирования и перемещения также очень похожи, поэтому вам нужно default их, если вы хотите их.

Ответ 2

Не решение, а один из возможных обходных путей. Вы можете наследовать все ваши классы от класса, который имеет только виртуальный деструктор по умолчанию.

Я проверил, используя GCC 9 и Apple Clang++ с -std=C++17: оба они генерируют конструкторы перемещения для классов, которые наследуют класс ниже.

class Object {
public:
    virtual ~Object() = default;
};

Класс ниже действительно будет иметь конструктор перемещения.

class Child : public Object {
public:
    Child(std::string data) : data(data) {
    }

private:
    std::string data;

};

Другой возможный, но рискованный обходной путь - вообще не объявлять виртуальных деструкторов. Это приведет к следующим рискам:

  • Все объекты всегда должны быть уничтожены кем-то, кто знает их точный тип. Что не очень большая проблема в красиво разработанном коде C++.
  • Когда объект такого класса хранится в контейнере, таком как std::vector или std::list он всегда должен быть упакован с использованием std::shared_ptr. std::unique_ptr приведет к утечкам! Это связано с их различиями, связанными с хранением удалителя.