web-dev-qa-db-fra.com

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

Au départ, je poste ceci sous la forme d'une question concernant uniquement les destructeurs, mais j'ajoute maintenant la prise en compte du constructeur par défaut. Voici la question initiale:

Si je veux donner à ma classe un destructeur virtuel, mais identique à ce que générerait le compilateur, 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 définition vide:

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

Existe-t-il un comportement différent de ces deux définitions?

Sur la base des réponses postées à cette question, la situation du constructeur par défaut semble similaire. Étant donné qu’il n’ya presque aucune différence de sens entre "=default" et "{} "pour les destructeurs, n’existe-t-il pas de même différence entre ces options pour les constructeurs par défaut? Autrement dit, si je veux créer un type dans lequel les objets de ce type seront à la fois créés et détruits, pourquoi voudrais-je dire

Widget() = default;

au lieu de

Widget() {}

?

Je m'excuse si l'extension de cette question après sa publication d'origine enfreint certaines règles SO. Publier une question presque identique pour les constructeurs par défaut m'a semblé l'option la moins souhaitable.

146
KnowItAllWannabe

C'est une question complètement différente lorsqu'on pose une question sur les constructeurs par rapport aux 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. La même chose est vraie des constructeurs.

En utilisant = default La syntaxe des fonctions membres spéciales (constructeur par défaut, constructeurs/affectations copier/déplacer, destructeurs, etc.) signifie quelque chose de très différent du simple fait de faire {}. Avec ce dernier, la fonction devient "fournie par l'utilisateur". Et ça change tout.

C'est une classe triviale selon la définition de C++ 11:

struct Trivial
{
  int foo;
};

Si vous essayez de construire par défaut, le compilateur générera automatiquement un constructeur par défaut. Il en va de même pour la copie/le mouvement et la destruction. Etant donné que l'utilisateur n'a fourni aucune de ces fonctions membres, la spécification C++ 11 considère qu'il s'agit d'une classe "triviale". Il est donc légal de le faire, comme de mémoriser leur contenu pour les initialiser, etc.

Cette:

struct NotTrivial
{
  int foo;

  NotTrivial() {}
};

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

Cette:

struct Trivial2
{
  int foo;

  Trivial2() = default;
};

Encore une fois, 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 par conséquent, le type est considéré comme trivial, car il ne possède pas de constructeur par défaut fourni par l'utilisateur.

Le = default La syntaxe est principalement utilisée pour effectuer des opérations telles que la copie des constructeurs/affectations, lorsque vous ajoutez des fonctions membres qui empêchent la création de telles fonctions. Mais il déclenche également un comportement spécial du compilateur, il est donc utile dans les constructeurs/destructeurs par défaut.

87
Nicol Bolas

Ils sont tous deux non-triviaux.

Ils ont tous les deux la même spécification noexcept, en fonction de 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
};

Puis le =default La solution sera compilée, mais Widget ne sera pas un type destructible. C'est à dire. si vous essayez de détruire un Widget, vous obtiendrez une erreur lors de la compilation. Mais si vous ne le faites pas, vous avez un programme de travail.

Otoh, si vous fournissez le destructeur fourni par l'utilisateur, les choses ne seront pas compilées, que vous détruisiez ou non un 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.
38
Howard Hinnant

La différence importante entre

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

et

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

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

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

un type spécial d’initialisation qui n’utilise pas de constructeur aura lieu et pour les types intégrés, cela se traduira par initialisation à zéro. Dans le 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 type de classe (éventuellement qualifié cv) (clause 9) avec un constructeur fourni par l'utilisateur (12.1), 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);

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

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

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

30
4pie0