Comment est "par défaut =" différent de "{}" pour défaut constructeur et le destructeur?

j'ai initialement posté ceci comme une question seulement au sujet des destructeurs, mais maintenant j'ajoute la considération du constructeur par défaut. Voici la question originale:

Si je veux donner ma classe un destructeur qui est virtuel, mais est sinon le même que 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 Dactylographie en utilisant un définition vide:

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

est-il possible que ces deux définitions se comportent différemment?

basé sur les 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, n'y a-t-il pas non plus de différence de sens entre ces options pour les constructeurs par défaut? C'est, en supposant que je veux pour créer un type où 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 étendre cette question après son affichage initial viole certaines règles SO. L'affichage d'une question presque identique pour les constructeurs par défaut m'a frappé comme l'option moins souhaitable.

126
demandé sur TemplateRex 2012-11-27 05:25:52

3 réponses

c'est une question complètement différente quand on pose sur les constructeurs que sur les destructeurs.

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

utilisant la syntaxe = default pour les fonctions spéciales des membres (par défaut constructeur, copier/déplacer des constructeurs ou d'une assignation, destructeurs etc) signifie quelque chose de très différent de simplement faire {} . Avec ce dernier, la fonction devient "l'utilisateur". Et cela change tout.

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

struct Trivial
{
  int foo;
};

si vous tentez 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/le mouvement et la destruction. Parce que l'utilisateur n'ayant fourni aucune de ces fonctions de membre, la spécification C++11 considère qu'il s'agit d'une classe "triviale". Il est donc légal de le faire, comme memcpy leurs contenus autour de les initialiser et ainsi de suite.

:

struct NotTrivial
{
  int foo;

  NotTrivial() {}
};

comme son nom l'indique, ce n'est plus anodin. Il a un constructeur par défaut fourni par l'utilisateur. Cela n'a pas d'importance si c'est vide; en ce qui concerne les règles de C++11, cela ne peut pas être un type trivial.

:

struct Trivial2
{
  int foo;

  Trivial2() = default;
};

encore une fois comme son nom l'indique, c'est un type trivial. Pourquoi? Parce que vous avez dit au compilateur de générer automatiquement le constructeur par défaut. Le constructeur 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.

la syntaxe = default est principalement là pour faire des choses comme des constructeurs de copie / assignation, quand vous ajoutez un membre les fonctions qui empêchent la création de telles fonctions. Mais il déclenche également un comportement spécial du compilateur, donc il est utile dans les constructeurs/destructeurs par défaut aussi.

74
répondu Nicol Bolas 2017-05-23 11:54:59

ils sont tous les deux non-trivial.

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
};

alors la solution =default sera compilée, mais Widget ne sera pas un type destructible. I. e. si vous essayez pour détruire un Widget , vous obtiendrez une erreur de compilation. Mais si vous ne le faites pas, vous avez un programme qui fonctionne.

Otoh, si vous fournissez le destructeur fourni par l'utilisateur , alors les choses ne se compileront pas 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.
37
répondu Howard Hinnant 2015-05-11 22:44:27

la différence importante entre

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

et

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

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

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

type spécial d'initialisation qui n'utilise pas de constructeur aura lieu et pour les types intégrés cela résultera dans zéro-initialisation . En cas de B(){} cela n'aura pas lieu. La norme C++ n3337 § 8.5/7 dit

valeur-initialiser un objet de type T signifie:

- Si T est a (éventuellement cv-qualifié) type de classe (Clause 9) avec un constructeur fourni par l'utilisateur (12.1), puis le constructeur par défaut pour T est appelé (et le l'initialisation est mal formé si T n'a pas de défaut accessible le constructeur);

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

- Si T est un type de tableau, alors chaque élément est valeur-initialisé; - 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

25
répondu 4pie0 2015-05-11 22:37:13