对于默认构造函数和析构函数,"= default"与"{}"有什么不同?

Kno*_*abe 151 c++ user-defined-functions default-constructor deleted-functions c++11

我最初发布这个只是关于析构函数的问题,但现在我正在添加对默认构造函数的考虑.这是原始问题:

如果我想给我的类一个虚拟的析构函数,但是与编译器生成的析构函数相同,我可以使用 =default:

class Widget {
public:
   virtual ~Widget() = default;
};
Run Code Online (Sandbox Code Playgroud)

但似乎我可以通过使用空定义减少输入来获得相同的效果:

class Widget {
public:
   virtual ~Widget() {}
};
Run Code Online (Sandbox Code Playgroud)

这两种定义的行为方式有何不同?

根据此问题的回复,默认构造函数的情况似乎相似.鉴于析构函数的" =default"和" {}"之间的含义几乎没有差异,默认构造函数的这些选项之间的含义几乎没有差别吗?也就是说,假设我想创建一个类型,其中该类型的对象将被创建和销毁,为什么我要说

Widget() = default;
Run Code Online (Sandbox Code Playgroud)

代替

Widget() {}
Run Code Online (Sandbox Code Playgroud)

如果在原始帖子违反某些SO规则后延长此问题,我深表歉意.为默认构造函数发布一个几乎完全相同的问题让我觉得不太理想.

Nic*_*las 91

在询问构造函数而不是析构函数时,这是一个完全不同的问题.

霍华德指出virtual,如果你的析构函数是,那么差异可以忽略不计.但是,如果你的析构函数是非虚拟的,那么这是一个完全不同的故事.构造函数也是如此.

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

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

struct Trivial
{
  int foo;
};
Run Code Online (Sandbox Code Playgroud)

如果尝试默认构造一个,编译器将自动生成默认构造函数.复制/移动和破坏也是如此.因为用户没有提供任何这些成员函数,所以C++ 11规范认为这是一个"普通"类.因此,这样做是合法的,比如记忆它们的内容以初始化它们等等.

这个:

struct NotTrivial
{
  int foo;

  NotTrivial() {}
};
Run Code Online (Sandbox Code Playgroud)

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

这个:

struct Trivial2
{
  int foo;

  Trivial2() = default;
};
Run Code Online (Sandbox Code Playgroud)

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

= default语法主要是有没有做的事情像拷贝构造函数/赋值,当您添加防止此类函数的创建成员函数.但它也会触发编译器的特殊行为,因此它在默认构造函数/析构函数中也很有用.

  • 因此,关键问题似乎是结果类是否是微不足道的,并且该问题的基础是*user-declared*(这是`= default`函数的情况)和*user-provided*的特殊函数之间的区别(这是`{}`)函数的情况.用户声明的函数和用户提供的函数都可以阻止生成其他特殊成员函数(例如,用户声明的析构函数会阻止生成移动操作),但只有用户提供的特殊函数才能使类变得非常重要.对? (2认同)
  • 尽管存在其他构造函数,但是`= default`似乎对强制编译器生成默认构造函数很有用;如果提供了任何其他用户声明的构造函数,则不会隐式声明默认构造函数。 (2认同)

How*_*ant 40

它们都是非平凡的.

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

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

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

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

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

Otoh,如果你提供用户提供的析构函数,那么无论你是否破坏了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.
Run Code Online (Sandbox Code Playgroud)

  • 有意思:换句话说,使用`= default;`除非使用析构函数,否则编译器不会生成析构函数,因此不会触发错误.这对我来说似乎很奇怪,即使不一定是个bug.我无法想象这个行为在标准中是*强制性的*. (9认同)
  • “然后 =default 解决方案将编译”不,不会。刚刚在 vs 中测试过 (2认同)
  • 错误消息是什么?VS 的版本是什么? (2认同)

4pi*_*ie0 30

之间的重要区别

class B {
    public:
    B(){}
    int i;
    int j;
};
Run Code Online (Sandbox Code Playgroud)

class B {
    public:
    B() = default;
    int i;
    int j;
};
Run Code Online (Sandbox Code Playgroud)

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

B* pb = new B();  // use of () triggers value-initialization
Run Code Online (Sandbox Code Playgroud)

将完全不使用构造函数的特殊类型的初始化将发生,对于内置类型,这将导致零初始化.如果B(){}不发生这种情况.C++标准n3337 § 8.5/7说

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

- 如果T是具有用户提供的构造函数 (12.1)的(可能是cv限定的)类类型(第9节),则调用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;
}
Run Code Online (Sandbox Code Playgroud)

可能的结果:

0,0
0,0
145084416,0
0,0
145084432,0
0,0
145084416,0
//...
Run Code Online (Sandbox Code Playgroud)

http://ideone.com/k8mBrd

  • @nawfelbgh 默认构造函数 A(){} 调用 std::string 的默认构造函数,因为这是非 POD 类型。std::string 的默认构造函数[将其初始化](http://stackoverflow.com/a/17738467/1141471) 为空、0 大小的字符串。标量的默认构造函数不执行任何操作:具有自动存储持续时间的对象(及其子对象)被初始化为不确定的值。 (3认同)
  • 为了清楚地说明上面的示例,B 类始终生成值 0:https://ideone.com/XOcHNq A: 0,0 B: 0,0 A: 145084416,0 B: 0,0 A: 145084432 ,0 B: 0,0 A: 145084416,0 //... (3认同)