Предупреждение: определение неявного конструктора копии устарело

У меня есть предупреждение в моем коде С++ 11, который я хотел бы исправить правильно, но я действительно не знаю, как это сделать. Я создал свой собственный класс исключений, который получен из std::runtime_error:

class MyError : public std::runtime_error
{
public:
    MyError(const std::string& str, const std::string& message)
      : std::runtime_error(message),
        str_(str)
    { }

    virtual ~MyError()
    { }

    std::string getStr() const
    {
        return str_;
    }

  private:
      std::string str_;
};

Когда я компилирую этот код с помощью clang-cl using /Wall я получаю следующее предупреждение:

warning: definition of implicit copy constructor for 'MyError' is deprecated 
         because it has a user-declared destructor [-Wdeprecated]

Поэтому, поскольку я определил деструктор в MyError никакой конструктор копии не будет создан для MyError. Я не совсем понимаю, вызвало ли это какие-либо проблемы...

Теперь я мог избавиться от этого предупреждения, просто удалив виртуальный деструктор, но я всегда думал, что производные классы должны иметь виртуальные деструкторы, если базовый класс (в данном случае std::runtime_error) имеет виртуальный деструктор.

Поэтому я думаю, что лучше не удалять виртуальный деструктор, а определять конструктор копирования. Но если мне нужно определить конструктор копирования, возможно, мне также следует определить оператор назначения копирования и конструктор перемещения и оператор назначения перемещения. Но это кажется излишним для моего простого класса исключения !?

Любые идеи, как лучше всего исправить эту проблему?

Ответ 1

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

§ 15.4 Деструкторы [class.dtor] (выделено мной)

Деструктор может быть объявлен виртуальным (13.3) или чисто виртуальным (13.4); если любые объекты этого класса или любого производного класса создаются в программа, деструктор должен быть определен. Если класс имеет базовый класс с виртуальным деструктором, его деструктором (будь то user- или неявно объявленный) является виртуальным.

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

Если вам не нужно что-то делать в своем деструкторе, лучшим способом будет просто пропустить явное объявление деструктора.

Если вам нужен собственный деструктор, и вы уверены, что копирование ctor по умолчанию, оператор присваивания копий, ctor перестановки и оператор присваивания перемещений будут делать для вас правильную вещь, лучше всего явно указать их по умолчанию следующим образом:

MyError(const MyError&) = default;
MyError(MyError&&) = default;
MyError& operator=(const MyError&) = default;
MyError& operator=(MyError&&) = default;

Несколько рассуждений о том, почему вы видите ошибку, потому что раньше это был правильный код в С++ 98:

Начиная с С++ 11 неявная генерация конструктора копирования объявлена устаревшей.

§ D.2 Неявное объявление функций копирования [depr.impldec]

Неявное определение конструктора копирования по умолчанию не рекомендуется, если у класса есть user- объявленный оператор присваивания копии или user- объявлен деструктором. Неявное определение копии оператор присваивания по умолчанию считается устаревшим, если класс имеет user- объявленный конструктор копирования или user- объявленный деструктор (15.4, 15.8). В будущем пересмотре этого международного стандарта эти неявные определения могут быть удалены (11.4).

Обоснованием этого текста является хорошо известное правило трех.

Все приведенные ниже цитаты получены из cppreference.com: https://en.cppreference.com/w/cpp/language/rule_of_three

Правило трех

Если классу требуется определенный деструктор user-, определенная копия user- конструктор, или user- определенный оператор присваивания копии, это почти безусловно, требует всех трех.

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

Для полноты ниже приводятся часто связанные правила 5 и несколько оспариваемое правило нуля

Правило пяти

Поскольку наличие деструктора, определенного user-, конструктор копирования, или оператор копирования-назначения предотвращает неявное определение перемещения конструктор и оператор присваивания перемещения, любой класс, для которого выполняется перемещение семантика желательна, должна объявить все пять специальных членов Функции:

Правило нуля

Классы, которые имеют собственные деструкторы, конструкторы копирования/перемещения или операторы копирования/перемещения должны иметь дело исключительно с правами собственности (что следует из принципа единой ответственности). Другие классы не должны иметь пользовательских деструкторов, конструкторов копирования/перемещения или операторы копирования/перемещения.

Ответ 2

Теперь я мог избавиться от этого предупреждения, просто удалив виртуальный деструктор, но я всегда думал, что производные классы должны иметь виртуальные деструкторы, если базовый класс (в данном случае std :: runtime_error) имеет виртуальный деструктор.

Ты подумал неправильно. Производные классы всегда будут иметь виртуальный деструктор, если вы определяете его в базе, независимо от того, создаете ли вы его явно или нет. Поэтому удаление деструктора было бы самым простым решением. Как вы можете видеть в документации для std::runtime_exception он также не предоставляет свой собственный деструктор, и он генерируется компилятором, потому что базовый класс std::exception имеет виртуальный dtor.

Но в случае, если вам нужен деструктор, вы можете явно добавить созданный компилятором экземпляр ctor:

MyError( const MyError & ) = default;

или запретить создание класса, не поддающегося копированию:

MyError( const MyError & ) = delete;

то же самое для оператора присваивания.

Ответ 3

Примечание: то же самое происходит для очень другого кода, но я пишу его здесь на случай, если кто-то получит такое же предупреждение.

Была ошибка в версиях 6.4 - 9.0 GCC, где использование объявлений для base_type operator = и base_type ctor в типах, унаследованных от base_type, который являлся шаблоном класса, фактически не создавало операторы копирования/перемещения ctor/operator (заканчивающиеся очень неожиданными ошибками компилятора, которые объект не может быть скопирован/перемещен).

Начиная с GCC 9.0, ошибка исправлена, но вместо этого создается это предупреждение. Предупреждение неверно и не должно появляться (использование явно объявляет конструкторы/операторы).