默认构造函数和析构函数的“ = default”与“ {}”有何不同?


169

我最初将此问题仅发布为关于析构函数的问题,但现在我添加了对默认构造函数的考虑。这是原始问题:

如果我想给我的类一个虚拟的析构函数,但在其他方面与编译器生成的析构函数相同,则可以使用=default

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

但是似乎我可以通过使用空定义进行更少的键入而获得相同的效果:

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

这两种定义有什么不同的表现方式吗?

根据针对该问题发布的答复,默认构造函数的情况似乎类似。既然对于析构函数,“ =default”和“ {}”之间的含义几乎没有差异,那么对于默认构造函数,这些选项之间的含义几乎也没有差异吗?也就是说,假设我要创建一个将同时创建和销毁该类型的对象的类型,为什么我要说

Widget() = default;

代替

Widget() {}

如果在发布原始问题后再提出此问题违反了某些SO规则,我深表歉意。为默认构造函数发布一个几乎相同的问题令我震惊,因为它是不太理想的选择。


1
我不知道,但是= default更明确的imo,并且与构造函数对其的支持一致。
克里斯,2012年

11
我不确定,但是我认为前者符合“琐碎的析构函数”的定义,而后者则不符合。所以,std::has_trivial_destructor<Widget>::valuetrue第一个,但false第二。我也不知道这意味着什么。:)
GManNickG 2012年

10
虚拟析构函数从来都不是小事。
吕克·丹顿

@LucDanton:我想睁开眼睛,看一下代码也可以!感谢您的纠正。
GManNickG 2012年

Answers:


103

当询问构造函数时,这是一个与析构函数完全不同的问题。

如霍华德指出virtual,如果您的析构函数是,则差异可以忽略不计。但是,如果您的析构函数不是虚拟的,那就完全不一样了。构造函数也是如此。

= default对特殊的成员函数(默认构造函数,复制/移动构造函数/赋值,析构函数等)使用语法意味着与简单地做有很大的不同{}。对于后者,该功能变为“用户提供”。这改变了一切。

根据C ++ 11的定义,这是一个琐碎的类:

struct Trivial
{
  int foo;
};

如果尝试默认构造一个,则编译器将自动生成一个默认构造函数。复制/移动和销毁也是如此。因为用户没有提供任何这些成员函数,所以C ++ 11规范将其视为“琐碎的”类。因此,这样做是合法的,例如将其内容memcpy初始化以此类推。

这个:

struct NotTrivial
{
  int foo;

  NotTrivial() {}
};

顾名思义,这不再是琐碎的事。它具有用户提供的默认构造函数。它是否为空无关紧要;就C ++ 11的规则而言,这不能是琐碎的类型。

这个:

struct Trivial2
{
  int foo;

  Trivial2() = default;
};

顾名思义,这是一个琐碎的类型。为什么?因为您告诉编译器自动生成默认构造函数。因此,构造函数不是“用户提供的”。因此,由于没有用户提供的默认构造函数,因此该类型被认为是微不足道的。

= default当添加阻止创建此类函数的成员函数时,该语法主要用于执行诸如复制构造函数/赋值之类的操作。但是它还会触发编译器的特殊行为,因此它在默认构造函数/析构函数中也很有用。


2
因此,关键问题似乎在于结果类是否琐碎,而该问题的根本在于用户声明的特殊功能(对于=default函数而言)与用户提供的(对于{})情形之间的区别。用户声明的函数和用户提供的函数都可以阻止其他特殊成员函数的生成(例如,用户声明的析构函数阻止移动操作的生​​成),但是只有用户提供的特殊函数才会使类变得平凡。对?
KnowItAllWannabe 2012年

@KnowItAllWannabe:这是总的想法,是的。
Nicol Bolas 2012年

我之所以选择此作为可接受的答案,只是因为它涵盖了构造函数和(通过引用霍华德的答案)析构函数。
KnowItAllWannabe

这里似乎是一个遗漏的词:“就C ++ 11的规则而言,您是微不足道的类型的权利”,我将对其进行修复,但我不能100%地确定意图是什么。
编码器

2
= default尽管存在其他构造函数,但似乎对于强制编译器生成默认构造函数很有用;如果提供了任何其他用户声明的构造函数,则不会隐式声明默认构造函数。
bgfvdu3w

42

他们都是不平凡的。

它们都具有相同的noexcept规范,具体取决于基础和成员的noexcept规范。

到目前为止,我检测到的唯一区别是,如果Widget包含具有不可访问或删除的析构函数的基或成员:

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

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

然后,=default解决方案将编译,但Widget不会是可破坏的类型。即,如果您尝试破坏Widget,则会出现编译时错误。但是,如果您不这样做,那么您就有一个有效的程序。

Otoh,如果您提供用户提供的析构函数,则无论您是否解构a,事情都不会编译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.

9
有趣的是:换句话说,=default;除非使用了编译器,否则编译器不会生成析构函数,因此不会触发错误。对我来说,这似乎很奇怪,即使不一定是错误。我无法想象此行为是标准中规定的。
Nik Bougalis 2012年

“然后= default解决方案将编译”不,它不会。刚刚在vs.
nano中

错误消息是什么,什么版本的VS?
霍华德·南南

35

之间的重要区别

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

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

是使用定义的默认构造函数B() = default;被认为不是用户定义的。这意味着在值初始化的情况下,如

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

会发生一种根本不使用构造函数的特殊初始化,对于内置类型,这将导致零初始化。在B(){}这种情况下将不会发生。C ++标准n3337 § 8.5 / 7说

值初始化类型T的对象意味着:

—如果T是具有用户提供的构造函数的(可能是cv限定的)类类型(第9条) (12.1)),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化格式错误) );

—如果T是一种(可能具有cv资格的)非工会类类型, 而没有用户提供的构造函数,则该对象将初始化为零,并且,如果T的隐式声明的默认构造函数非平凡,则将调用该构造函数。

—如果T是数组类型,则每个元素都进行值初始化;—否则,该对象将被初始化为零。

例如:

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

可能的结果:

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

http://ideone.com/k8mBrd


然后,为什么“ {}”和“ = default”总是初始化std :: string ideone.com/LMv5Uf
nawfel bgh '02

1
@nawfelbgh默认构造函数A(){}调用std :: string的默认构造函数,因为这是非POD类型。std :: string的默认ctor 将其初始化为0大小的空字符串。标量的默认ctor不执行任何操作:具有自动存储持续时间的对象(及其子对象)被初始化为不确定的值。
4pie0'6
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.