web-dev-qa-db-fra.com

Avertissement: la définition du constructeur de copie implicite est obsolète

J'ai un avertissement dans mon code C++ 11 que je voudrais corriger correctement mais je ne sais pas vraiment comment. J'ai créé ma propre classe d'exceptions dérivée de 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_;
};

Quand je compile ce code avec clang-cl en utilisant /Wall Je reçois l'avertissement suivant:

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

Donc parce que j'ai défini un destructeur dans MyError aucun constructeur de copie ne sera généré pour MyError. Je ne comprends pas très bien si cela causera des problèmes ...

Maintenant, je pouvais me débarrasser de cet avertissement en supprimant simplement le destructeur virtuel, mais j'ai toujours pensé que les classes dérivées devraient avoir des destructeurs virtuels si la classe de base (dans ce cas std::runtime_error) possède un destructeur virtuel.

Par conséquent, je suppose qu'il vaut mieux ne pas supprimer le destructeur virtuel mais définir le constructeur de copie. Mais si j'ai besoin de définir le constructeur de copie, je devrais peut-être aussi définir l'opérateur d'affectation de copie et le constructeur de déplacement et l'opérateur d'affectation de déplacement. Mais cela semble exagéré pour ma classe d'exception simple!?

Des idées sur la meilleure façon de résoudre ce problème?

11
Linoliumz

Vous n'avez pas besoin de déclarer explicitement le destructeur dans une classe dérivée:

§ 15.4 Destructeurs [class.dtor] (accent sur le mien)

Un destructeur peut être déclaré virtuel (13.3) ou pur virtuel (13.4); si des objets de cette classe ou de toute classe dérivée sont créés dans le programme, le destructeur doit être défini. Si une classe a une classe de base avec un destructeur virtuel, son destructeur (qu'il soit déclaré par l'utilisateur ou implicitement) est virtuel .

En fait, cela peut même être préjudiciable aux performances dans certains cas, car déclarer explicitement un destructeur empêchera la génération implicite d'un constructeur de mouvement et d'un opérateur d'affectation de mouvement.

À moins que vous n'ayez besoin de faire quelque chose dans votre destructeur, la meilleure solution serait d'omettre simplement une déclaration explicite d'un destructeur.

Si vous avez besoin d'un destructeur personnalisé et êtes certain que le ctor de copie par défaut, l'opérateur d'affectation de copie, le ctor de déplacement et l'opérateur d'affectation de déplacement feraient la bonne chose pour vous, il est préférable de les définir explicitement par défaut comme ceci:

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

Quelques raisons pour expliquer pourquoi vous voyez l'erreur, car il s'agissait auparavant d'un code parfaitement valide en C++ 98:

Depuis C++ 11, la génération implicite du constructeur de copie est déclarée obsolète.

§ D.2 Déclaration implicite des fonctions de copie [depr.impldec]

La définition implicite d'un constructeur de copie par défaut est déconseillée si la classe a un opérateur d'affectation de copie déclaré par l'utilisateur ou un destructeur déclaré par l'utilisateur. La définition implicite d'un opérateur d'affectation de copie par défaut est déconseillée si la classe a un constructeur de copie déclaré par l'utilisateur ou un destructeur déclaré par l'utilisateur (15.4, 15.8). Lors d'une future révision de la présente Norme internationale, ces définitions implicites pourraient être supprimées (11.4).

La raison d'être de ce texte est la règle bien connue des trois.

Toutes les citations ci-dessous proviennent de cppreference.com: https://en.cppreference.com/w/cpp/language/rule_of_three

Règle de trois

Si une classe nécessite un destructeur défini par l'utilisateur, un constructeur de copie défini par l'utilisateur ou un opérateur d'affectation de copie défini par l'utilisateur, elle requiert presque certainement les trois.

La raison pour laquelle cette règle empirique existe est que le dtor généré par défaut, le ctor de copie et l'opérateur d'affectation pour gérer différents types de ressources (notamment des pointeurs vers la mémoire, mais aussi d'autres, comme les descripteurs de fichiers et les sockets réseau pour n'en nommer que quelques-uns) rarement faire le bon comportement. Si le programmeur pensait qu'il avait besoin d'une manipulation spéciale pour la fermeture d'un descripteur de fichier dans le destructeur de classe, il veut certainement définir comment cette classe doit être copiée ou déplacée.

Pour être complet, voici la règle de 5 souvent liée, et la règle de zéro quelque peu contestée

Règle de cinq

Étant donné que la présence d'un destructeur, d'un constructeur de copie ou d'un opérateur d'affectation de copie défini par l'utilisateur empêche la définition implicite du constructeur de déplacement et de l'opérateur d'affectation de déplacement, toute classe pour laquelle la sémantique de déplacement est souhaitable doit déclarer les cinq fonctions membres spéciales:

Règle de zéro

Les classes qui ont des destructeurs personnalisés, des constructeurs de copie/déplacement ou des opérateurs d'affectation de copie/déplacement doivent traiter exclusivement de la propriété (qui découle du principe de responsabilité unique). Les autres classes ne devraient pas avoir de destructeurs personnalisés, de constructeurs de copie/déplacement ou d'opérateurs d'affectation de copie/déplacement.

9
divinas

Maintenant, je pouvais me débarrasser de cet avertissement en supprimant simplement le destructeur virtuel, mais j'ai toujours pensé que les classes dérivées devraient avoir des destructeurs virtuels si la classe de base (dans ce cas std :: runtime_error) a un destructeur virtuel.

Tu as mal pensé. Les classes dérivées auront toujours un destructeur virtuel si vous en définissez un dans la base, que vous le créiez explicitement ou non. La suppression du destructeur serait donc la solution la plus simple. Comme vous pouvez le voir dans documentation for std::runtime_exception il ne fournit pas non plus son propre destructeur et il est généré par le compilateur car la classe de base std::exception a un dtor virtuel.

Mais au cas où vous auriez besoin d'un destructeur, vous pouvez explicitement ajouter un ctor de copie généré par le compilateur:

MyError( const MyError & ) = default;

ou lui interdire de rendre la classe non copiable:

MyError( const MyError & ) = delete;

de même pour l'opérateur d'affectation.

5
Slava