关于c ++:对于默认构造函数和析构函数,“= default”与“{}”有什么不同?

How is “=default” different from “{}” for default constructor and destructor?

我最初只是把它作为一个关于析构函数的问题发布,但现在我要添加对默认构造函数的考虑。以下是最初的问题:

If I want to give my class a destructor that is virtual, but is
otherwise the same as what the compiler would generate, I can use =default:

1
2
3
4
class Widget {
public:
   virtual ~Widget() = default;
};

But it seems that I can get the same effect with less typing using an
empty definition:

1
2
3
4
class Widget {
public:
   virtual ~Widget() {}
};

Is there any way in which these two definitions behave differently?

基于针对这个问题发布的回复,默认构造函数的情况似乎类似。假设析构函数的"EDOCX1"(0)和"EDOCX1"(1)的含义几乎没有区别,那么默认构造函数的这些选项的含义也几乎没有区别吗?也就是说,假设我想创建一个类型,该类型的对象将同时被创建和销毁,我为什么要说

1
Widget() = default;

而不是

1
Widget() {}

如果在最初发布后扩展此问题违反了某些SO规则,我深表歉意。对于默认的构造函数发布一个几乎相同的问题让我觉得这是一个不太理想的选择。


这是一个与析构函数完全不同的问题。

如果你的析构函数是virtual,那么差异是可以忽略的,正如霍华德指出的。但是,如果您的析构函数是非虚拟的,那么情况就完全不同了。构造函数也是如此。

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

这是C++ 11定义的一个小类:

1
2
3
4
struct Trivial
{
  int foo;
};

如果尝试使用默认的构造方法,编译器将自动生成默认的构造方法。复制/移动和销毁也是如此。因为用户没有提供这些成员函数中的任何一个,所以C++ 11规范认为这是一个"琐碎"类。因此,这样做是合法的,就像memcpy通过它们周围的内容来初始化它们等等。

这是:

1
2
3
4
5
6
struct NotTrivial
{
  int foo;

  NotTrivial() {}
};

顾名思义,这不再是微不足道的。它有一个用户提供的默认构造函数。如果它是空的并不重要,就C++ 11的规则而言,这不能是微不足道的类型。

这是:

1
2
3
4
5
6
struct Trivial2
{
  int foo;

  Trivial2() = default;
};

顾名思义,这是一个微不足道的类型。为什么?因为您告诉编译器自动生成默认的构造函数。因此,构造函数不是"由用户提供的"。因此,类型计算起来很简单,因为它没有用户提供的默认构造函数。

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


它们都是不平凡的。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct A
{
private:
    ~A();
};

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

然后将编译=default解决方案,但Widget不是可销毁类型。也就是说,如果你试图破坏一个Widget,你会得到一个编译时错误。但如果你不这样做,你就有了一个有效的计划。

但是,如果您提供用户提供的析构函数,那么无论您是否销毁Widget,都不会编译:

1
2
3
4
5
6
7
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.


两者之间的重要区别

1
2
3
4
5
6
class B {
    public:
    B(){}
    int i;
    int j;
};

1
2
3
4
5
6
class B {
    public:
    B() = default;
    int i;
    int j;
};

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

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

将发生完全不使用构造函数的特殊类型的初始化,对于内置类型,这将导致零初始化。如果是B(){},这不会发生。C++标准N33 37×8 5/7表示

To value-initialize an object of type T means:

— if T is a (possibly
cv-qualified) class type (Clause 9) with a user-provided constructor
(12.1), then the default constructor for T is called (and the
initialization is ill-formed if T has no accessible default
constructor);

— if T is a (possibly cv-qualified) non-union class type
without a user-provided constructor, then the object is
zero-initialized and, if T’s implicitly-declared default constructor
is non-trivial, that constructor is called.

— if T is an array type,
then each element is value-initialized; — otherwise, the object is
zero-initialized.

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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;
}

可能的结果:

1
2
3
4
5
6
7
8
0,0
0,0
145084416,0
0,0
145084432,0
0,0
145084416,0
//...

http://ideone.com/k8mbrd