对于默认构造函数和析构函数,“=default”与“”有何不同?

Posted

技术标签:

【中文标题】对于默认构造函数和析构函数,“=default”与“”有何不同?【英文标题】:How is "=default" different from "" for default constructor and destructor?对于默认构造函数和析构函数,“=default”与“”有何不同? 【发布时间】:2012-11-27 01:25:52 【问题描述】:

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

如果我想给我的班级一个虚拟的析构函数,但是 否则和编译器生成的一样,我可以使用=default:

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

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

class Widget 
public:
   virtual ~Widget() 
;

这两个定义有什么不同的表现吗?

根据针对此问题发布的回复,默认构造函数的情况似乎相似。鉴于析构函数的“=default”和“”在含义上几乎没有区别,那么默认构造函数的这些选项之间的含义是否也几乎没有区别?也就是说,假设我想创建一个类型,该类型的对象将被创建和销毁,我为什么要说

Widget() = default;

而不是

Widget() 

?

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

【问题讨论】:

据我所知,= default 更明确,并且与构造函数对它的支持一致。 我不确定,但我认为前者符合“平凡析构函数”的定义,而后者不符合。所以std::has_trivial_destructor<Widget>::value 第一个是true,第二个是false。这意味着什么我也不知道。 :) 虚拟析构函数绝不是微不足道的。 @LucDanton:我想睁开眼睛看看代码也可以!谢谢指正。 相关:***.com/questions/20828907/… 【参考方案1】:

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

如果你的析构函数是virtual,那么差别可以忽略不计,as Howard pointed out。但是,如果您的析构函数是非虚拟的,那就完全不同了。构造函数也是如此。

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

这是 C++11 定义的一个微不足道的类:

struct Trivial

  int foo;
;

如果您尝试使用默认构造函数一,编译器将自动生成默认构造函数。复制/移动和破坏也是如此。因为用户没有提供任何这些成员函数,C++11 规范认为这是一个“微不足道的”类。因此,这样做是合法的,比如 memcpy 它们的内容以初始化它们等等。

这个:

struct NotTrivial

  int foo;

  NotTrivial() 
;

顾名思义,这不再是微不足道的。它有一个用户提供的默认构造函数。是否为空也没关系;就 C++11 的规则而言,这不可能是一个平凡的类型。

这个:

struct Trivial2

  int foo;

  Trivial2() = default;
;

顾名思义,这是一种微不足道的类型。为什么?因为您告诉编译器自动生成默认构造函数。因此构造函数不是“用户提供的”。因此,类型算得上是微不足道的,因为它没有用户提供的默认构造函数。

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

【讨论】:

所以关键问题似乎是生成的类是否微不足道,而这个问题的根本在于 user-declared 的特殊函数之间的区别(即=default 函数)和 user-provided 就是这种情况)函数。用户声明的和用户提供的函数都可以阻止其他特殊成员函数的生成(例如,用户声明的析构函数阻止移动操作的生​​成),但只有用户提供的特殊函数才能呈现一个非平凡的类。对吗? @KnowItAllWannabe:这是大意,是的。 我选择这个作为接受的答案,只是因为它涵盖了构造函数和(参考霍华德的答案)析构函数。 这里似乎是一个缺失的词“就 C++11 的规则而言,你拥有微不足道的权利”我会修复它,但我不是 quite 100 % 确定是什么意思。 = default 似乎有助于强制编译器生成默认构造函数,尽管存在其他构造函数;如果提供了任何其他用户声明的构造函数,则不会隐式声明默认构造函数。【参考方案2】:

它们都是不平凡的。

根据基类和成员的 noexcept 规范,它们都有相同的 noexcept 规范。

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

struct A

private:
    ~A();
;

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

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

Otoh,如果您提供 user-provided 析构函数,那么无论您是否销毁 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.

【讨论】:

有趣:换句话说,=default; 编译器不会生成析构函数,除非它被使用,因此不会触发错误。这对我来说似乎很奇怪,即使不一定是错误。我无法想象这种行为在标准中是强制 "然后 =default 解决方案将编译" 不,它不会。刚刚在 vs 中测试过。 错误信息是什么,VS的版本是什么? 如果我没记错的话,你需要在堆上分配对象才能成为一个有效的测试——声明一个本地的Widget会自动尝试销毁它,所以它不会编译,但是只要你从来没有delete它,写new Widget就可以在=default的情况下工作。【参考方案3】:

两者的重要区别

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

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

使用B() = default; 定义的默认构造函数是否被视为非用户定义。这意味着在 value-initialization 的情况下,如

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

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

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

——如果 T 是一个(可能 cv-qualified) 类类型(第 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 ? @nawfelbgh 默认构造函数 A() 调用 std::string 的默认构造函数,因为这是非 POD 类型。 std::string initializes it 的默认 ctor 为空,0 大小的字符串。标量的默认 ctor 什么都不做:具有自动存储持续时间的对象(及其子对象)被初始化为不确定的值。 只是为了清楚上面的例子,B类总是导致值0:ideone.com/XOcHNq A:0,0 B:0,0 A:145084416,0 B:0,0 A : 145084432,0 B: 0,0 A: 145084416,0 //...

以上是关于对于默认构造函数和析构函数,“=default”与“”有何不同?的主要内容,如果未能解决你的问题,请参考以下文章

C++构造和析构

3-3:类与对象中篇——默认成员函数之构造函数和析构函数

CPP_类默认函数:构造函数,拷贝构造函数,赋值函数和析构函数

构造函数和析构函数

C语言里面构造函数和析构函数的运用办法

9. 构造函数和析构函数