159 votes

En quoi "=default" est-il différent de "{}" pour le constructeur et le destructeur par défaut ?

À l'origine, j'ai posté cette question en tant que question portant uniquement sur les destructeurs, mais j'y ajoute maintenant l'examen du constructeur par défaut. Voici la question originale :

Si je veux donner à ma classe un destructeur qui est virtuel, mais qui est mais qui est par ailleurs identique à ce que le compilateur générerait, je peux utiliser =default :

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

Mais il semble que je puisse obtenir le même effet avec moins de frappe en utilisant une fonction définition vide :

class Widget {
public:
   virtual ~Widget() {}
};

Ces deux définitions se comportent-elles différemment ?

Sur la base des réponses postées pour cette question, la situation pour le constructeur par défaut semble similaire. Étant donné qu'il n'y a pratiquement aucune différence de sens entre " =default " et " {} "Pour les destructeurs, il n'y a pas non plus de différence de sens entre ces options pour les constructeurs par défaut ? En d'autres termes, en supposant que je veuille créer un type dont les objets seront à la fois créés et détruits, pourquoi voudrais-je dire

Widget() = default;

au lieu de

Widget() {}

?

Je m'excuse si le fait de prolonger cette question après sa publication initiale enfreint certaines règles de l'OS. Poser une question presque identique pour les constructeurs par défaut m'a semblé être l'option la moins souhaitable.

95voto

Nicol Bolas Points 133791

La question est complètement différente selon qu'il s'agit de constructeurs ou de destructeurs.

Si votre destructeur est virtual alors la différence est négligeable, comme l'a souligné Howard . Cependant, si votre destructeur était non-virtuel c'est une histoire complètement différente. Il en va de même pour les constructeurs.

Utilisation de = default pour les fonctions spéciales des membres (constructeur par défaut, constructeurs/affectations de copie/déplacement, destructeurs, etc.) signifie quelque chose de très différent que de simplement faire {} . Avec ce dernier, la fonction devient "fournie par l'utilisateur". Et cela change tout.

Il s'agit d'une classe triviale selon la définition de C++11 :

struct Trivial
{
  int foo;
};

Si vous essayez d'en construire un par défaut, le compilateur générera automatiquement un constructeur par défaut. Il en va de même pour la copie/déplacement et la destruction. Parce que l'utilisateur n'a fourni aucune de ces fonctions membres, la spécification C++11 considère cette classe comme "triviale". Il est donc légal de faire cela, comme memcpy leur contenu autour pour les initialiser et ainsi de suite.

Ceci :

struct NotTrivial
{
  int foo;

  NotTrivial() {}
};

Comme son nom l'indique, ce n'est plus anodin. Il a un constructeur par défaut qui est fourni par l'utilisateur. Peu importe qu'il soit vide ; en ce qui concerne les règles de C++11, il ne peut pas s'agir d'un type trivial.

Ceci :

struct Trivial2
{
  int foo;

  Trivial2() = default;
};

Comme son nom l'indique, il s'agit d'un type trivial. Pourquoi ? Parce que vous avez demandé au compilateur de générer automatiquement le constructeur par défaut. Le constructeur n'est donc pas "fourni par l'utilisateur". Et donc, le type compte comme trivial, puisqu'il n'a pas de constructeur par défaut fourni par l'utilisateur.

Le site = default La syntaxe est principalement là pour faire des choses comme la copie des constructeurs/affectations, lorsque vous ajoutez des fonctions membres qui empêchent la création de telles fonctions. Mais elle déclenche également un comportement spécial de la part du compilateur, de sorte qu'elle est également utile dans les constructeurs/destructeurs par défaut.

41voto

Howard Hinnant Points 59526

Ils sont tous deux non triviaux.

Ils ont tous deux la même spécification noexcept selon la spécification noexcept des bases et des membres.

La seule différence que je détecte jusqu'à présent est que si Widget contient une base ou un membre avec un destructeur inaccessible ou supprimé :

struct A
{
private:
    ~A();
};

class Widget {
    A a_;
public:
#if 1
   virtual ~Widget() = default;
#else
   virtual ~Widget() {}
#endif
};

Ensuite, le =default compilera, mais Widget ne sera pas de type destructible. Par exemple, si vous essayez de détruire un type Widget vous obtiendrez une erreur de compilation. Mais si vous ne le faites pas, vous avez un programme qui fonctionne.

En revanche, si vous fournissez le fourni par l'utilisateur destructeur, alors les choses ne compileront pas, que vous détruisiez ou non un objet de type Widget :

test.cpp:8:7: error: field of type 'A' has private destructor
    A a_;
      ^
test.cpp:4:5: note: declared private here
    ~A();
    ^
1 error generated.

32voto

bits_international Points 13725

La différence importante entre

class B {
    public:
    B(){}
    int i;
    int j;
};

et

class B {
    public:
    B() = default;
    int i;
    int j;
};

est le constructeur par défaut défini avec B() = default; est considéré non défini par l'utilisateur . Cela signifie qu'en cas de initialisation de la valeur dans le cas de

B* pb = new B();  // use of () triggers value-initialization

un type spécial d'initialisation qui n'utilise pas du tout de constructeur aura lieu et pour les types intégrés, cela se traduira par Initialisation zéro . En cas de B(){} cela n'aura pas lieu. La norme C++ n3337 § 8,5/7 dit

Initialiser en valeur un objet de type T signifie :

- si T est un (éventuellement cv-qualifié) (clause 9). avec un constructeur fourni par l'utilisateur (12.1), alors le constructeur par défaut de T est appelé (et l'initialisation est mal formée si T n'a pas de constructeur par défaut accessible). initialisation est mal formée si T n'a pas de constructeur par défaut accessible. accessible) ;

- si T est un type de classe non syndiqué (éventuellement qualifié de cv) sans un constructeur fourni par l'utilisateur alors l'objet est zéro initialisé et, si le constructeur par défaut implicitement déclaré de T est non-trivial, ce constructeur est appelé.

- si T est un type de tableau, alors chaque élément est initialisé par sa valeur ; - sinon, l'objet est zéro initialisation.

Par exemple :

#include <iostream>

class A {
    public:
    A(){}
    int i;
    int j;
};

class B {
    public:
    B() = default;
    int i;
    int j;
};

int main()
{
    for( int i = 0; i < 100; ++i) {
        A* pa = new A();
        B* pb = new B();
        std::cout << pa->i << "," << pa->j << std::endl;
        std::cout << pb->i << "," << pb->j << std::endl;
        delete pa;
        delete pb;
    }
  return 0;
}

résultat possible :

0,0
0,0
145084416,0
0,0
145084432,0
0,0
145084416,0
//...

http://ideone.com/k8mBrd

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X