构造函数“=default”和C++中编译器生成的构造函数有什么区别?

Noy*_*a_G 41 c++ constructor default-constructor

代码示例:

class Dog
{
private:
    int x;
public:
    Dog()=default;
};
Run Code Online (Sandbox Code Playgroud)

对比 这段代码:

class Dog
{
private:
    int x;
};
Run Code Online (Sandbox Code Playgroud)

“=default”的构造函数(第一个代码)和编译器创建的构造函数(如第二个代码)有什么区别?

for*_*818 38

Dog() = default;是用户声明的构造函数(不要与用户定义的构造函数混淆)。它是默认的默认构造函数。通常,当类具有其他构造函数但您仍然希望编译器生成默认构造函数(或者更确切地说是“默认的默认构造函数”)时,您会使用它。这是 C++ 的最佳术语。注意这两个“默认”如何略微不同不同的意思)。

用户声明的构造函数阻止类成为聚合。来自cppreference,仅适用于 C++20:

聚合是以下类型之一:

  • 数组类型
  • 类类型(通常,结构或联合),具有
    • 没有私有或受保护的直接非静态数据成员
    • 没有用户声明或继承的构造函数
    • 没有虚拟、私有或受保护的基类
    • 没有虚成员函数

例如,请考虑:

#include <iostream>
#include <type_traits>

class Dog {
    int x;
public:
    Dog()=default;
};

class Horse {
    int x;
};

class Swan {
public: 
    int x;
};

class Cow {
public:
    int x;
    Cow() = default;
};

int main() {
    std::cout << std::is_aggregate_v<Dog>;
    std::cout << std::is_aggregate_v<Horse>;
    std::cout << std::is_aggregate_v<Swan>;
    std::cout << std::is_aggregate_v<Cow>;
}
Run Code Online (Sandbox Code Playgroud)

输出

0010
Run Code Online (Sandbox Code Playgroud)

前两个DogHorse类似于您的两个版本的Dog. 它们不是聚合,因为它们具有私有成员。Swan是一个聚合,但Cow不是,因为它有一个用户声明的构造函数。

与聚合一起工作但不适用于非聚合的东西是指定的初始值设定项(相同的 cppreference 页面):

Swan s{.x=3};    // OK
Cow c{.x=4};     // Error: Cow is not an aggregate
Run Code Online (Sandbox Code Playgroud)

TL;DR:我不知道你的两个Dogs之间有什么区别,但一般来说,用户声明的构造函数的存在会有所不同。

  • `默认的默认构造函数` 人们说 C++ 是一种令人困惑的语言 (8认同)
  • @Peter-ReinstateMonica:默认构造函数可以在类声明之外默认设置,在单个编译单元中,并且对该类的所有其他使用者不可见。因此,C++ 必须在(a)类内部和外部默认值相同并使类成为非聚合或(b)类内部默认值与编译器生成相同且类外部使类非聚合之间进行选择。 (4认同)

chr*_*ris 15

我将范围限制为默认构造函数,如问题的代码和标签。在大多数情况下,您将获得相同的效果,因为= default;松散的意思是“给我编译器生成的”。需要注意的是很重要的,究竟有没有申报

如果类 X 没有用户声明的构造函数,则没有参数的非显式构造函数被隐式声明为默认值。隐式声明的默认构造函数是其类的内联公共成员。

如果您的声明更改了其中任何一项,它将不再与隐式声明完全相同。在标准中,Dog() = default;用户声明的构造函数,而不是用户提供的构造函数。拥有用户声明的默认构造函数和没有构造函数之间存在一些小的差异。

如前所述,聚合是固定的

struct not_agg {
    not_agg() = delete;
    int x;
};
Run Code Online (Sandbox Code Playgroud)

在修复之前,这样的类可以通过集合初始化创建:not_agg{}。当然,这也扩展到= default;. 每[dcl.init.aggr]

聚合是一个数组或一个类

  • 没有用户声明或继承的构造函数

附件 C 中也给出了这种变化的理由:

删除潜在的容易出错的聚合初始化,尽管声明了类的构造函数,但它可能适用。


一个有趣但非常微小的区别是具有用户声明的构造函数的类不允许具有与该类同名的非静态数据成员:

class c {
    int c; // Okay
};

class c2 {
    c2() = default;
    int c2; // Error
};
Run Code Online (Sandbox Code Playgroud)

这是由于[class.mem]/21

此外,如果类 T 具有用户声明的构造函数,则类 T 的每个非静态数据成员都应具有与 T 不同的名称。

  • @StoryTeller-UnslanderMonica,哦,天哪,那真是一个快乐的巧合。 (3认同)
  • `c` - 考虑到这条规则的起源,不错的名字选择。 (2认同)

S.M*_*.M. 4

  1. 默认声明的构造函数可以通过受保护或私有访问进行保护。未声明的默认构造函数始终是其类的内联公共成员。
  2. = default构造函数像通常的成员函数一样实例化,可以是内联的也可以不是,在第二种情况下它们具有强链接引用。编译器将未声明的默认构造函数创建为具有弱链接引用的内联成员函数。
  3. 正如注释中提到的,=default构造函数是用户定义的构造函数,它们的类不是聚合类型。

  • 这并没有真正回答两个类定义之间是否存在差异的问题。 (5认同)
  • @Peter-ReinstateMonica 在类中声明不带 `=default`,然后在定义中添加 `=default`。但这使它失去了它的神奇属性(例如,值初始化的行为与值初始化不同)。 (2认同)