【问题标题】:What is the difference between constructor "=default" and the compiler generated constructor in C++?构造函数“=default”和C++中编译器生成的构造函数有什么区别?
【发布时间】:2021-10-19 14:36:46
【问题描述】:

代码示例:

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

对比。这段代码:

class Dog
{
private:
    int x;
};

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

【问题讨论】:

  • 至少有一个区别:在 C++20 中,拥有用户声明的构造函数(即使您使用默认实现)将意味着您的类不再算作聚合。
  • 你想知道这两个构造函数的区别(如标题所示)还是使用=default的原因(如正文所示)。
  • 您关心哪个版本的C++?不同版本的标准对行为的处理方式不同
  • 这能回答你的问题吗? The new syntax "= default" in C++11

标签: c++ constructor default-constructor


【解决方案1】:
  1. 可以使用受保护或私有访问来保护默认声明的构造函数。未声明的默认构造函数始终是其类的内联公共成员。
  2. = default 构造函数像通常的成员函数一样被实例化,可能是内联的,也可能不是内联的,在第二种情况下它们具有强链接引用。编译器将未声明的默认构造函数创建为具有弱链接引用的内联成员函数。
  3. 如 cmets 中所述,=default 构造函数是用户定义的构造函数,它们的类不是聚合类型。

【讨论】:

  • 这并不能真正回答两个类定义之间是否存在差异的问题。
  • 有这样的问题吗? 构造函数=default(第一个代码)与编译器创建的构造函数(如第二个代码)有什么不同我不明白两个类定义之间的区别.
  • 有趣:如何使=default 构造函数不内联?
  • @Peter-ReinstateMonica 在没有=default的类中声明,然后在定义中添加=default。但是,它使它失去了它的神奇属性(例如,值初始化与值初始化的行为不同)。
  • @HolyBlackCat 我不知道这是可能的。这就解释了为什么默认的默认构造函数没有与其他用户定义的构造函数区分开来并相应地更改类的属性:编译器通常无法知道用户定义的构造函数是如何实现的。
【解决方案2】:

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

输出

0010

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

适用于聚合但不适用于非聚合的东西被指定为初始化器(相同的 cppreference 页面):

Swan s{.x=3};    // OK
Cow c{.x=4};     // Error: Cow is not an aggregate

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

【讨论】:

  • defaulted default constructor 人们说 C++ 是一门令人困惑的语言
  • 有什么理由(哦,我的——为什么!)标准作者选择拒绝具有默认默认构造函数聚合状态的类?毕竟,它只是在表达一直存在的东西。
  • @Peter-ReinstateMonica 据我了解,它与 C++11 的向后兼容性有关,或者当相同的代码行为不同时避免意外。我再也找不到提到它的答案了。我必须先做一些研究,然后才能为这个答案添加一些解释
  • @SilvioMayolo 区分具体默认默认构造函数和默认默认默认构造函数。您所需要的只是多一层抽象;)
  • @Peter-ReinstateMonica:默认构造函数可以在类声明之外,在单个编译单元中默认,并且对类的所有其他使用者不可见。因此,C++ 必须在 (a) 类内部和外部的默认值相同并使类不聚合或 (b) 类内部的默认值与编译器生成的相同和类外部的非聚合之间做出选择。
【解决方案3】:

我会将范围限制为默认构造函数,就像问题的代码和标记中一样。在大多数情况下,您将获得相同的效果,因为= default; 松散地表示“给我编译器生成的”。重要的是要注意 what exactly 没有声明 does

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

如果您的声明更改了其中任何一项,它将不再与隐式声明完全相同。在标准中,Dog() = default;user-declared 构造函数,但不是 user-provided 构造函数。有一个用户声明的默认构造函数和没有构造函数之间有一些小的区别。

如前所述,聚合were fixed

struct not_agg {
    not_agg() = delete;
    int x;
};

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

聚合是一个数组或一个类

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

附件 C 中也给出了此更改的理由:

删除可能容易出错的聚合初始化,尽管有 类的声明构造函数。


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

class c {
    int c; // Okay
};

class c2 {
    c2() = default;
    int c2; // Error
};

这是由于[class.mem]/21:

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

【讨论】:

  • c - 很好的名字选择,考虑到这条规则的起源。
  • @StoryTeller-UnslanderMonica,天哪,真是巧合。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-23
  • 2016-10-16
  • 1970-01-01
  • 2019-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多