声明 empty/default 构造函数的几种方法之间的区别

Differences between several ways to declare an empty/default constructor

在 C++14 中有几种声明空构造函数的方法

class C1 {
    int* ptr;
    int val;
};

class C2 {
    int* ptr = nullptr;
    int val = 0;
};

class C3 {
    constexpr C3() noexcept = default;
    int* ptr;
    int val;
};

class C4 {
    constexpr C4() noexcept = default;
    int* ptr = nullptr;
    int val = 0;
};

class C5 {
    constexpr C5() noexcept : ptr{nullptr}, val{0} = default;
    int* ptr;
    int val;
};

class C6 {
    constexpr C6() noexcept : ptr{nullptr}, val{0} {}
    int* ptr;
    int val;
};

class C7 {
    constexpr C7() noexcept;
    int* ptr;
    int val;
};
constexpr C7::C7() noexcept = default;

class C8 {
    constexpr C8() noexcept;
    int* ptr = nullptr;
    int val = 0;
};
constexpr C8::C8() noexcept = default;

class C9 {
    constexpr C9() noexcept;
    int* ptr;
    int val;
};
constexpr C9::C9() noexcept : ptr{nullptr}, val{0} = default;

class C10 {
    constexpr C10() noexcept;
    int* ptr;
    int val;
};
constexpr C10::C10() noexcept : ptr{nullptr}, val{0} {}

我想知道,所有这些 类 和严格等效的 类 之间的确切区别是什么,并且会根据 C++ 标准产生完全相同的行为。

这些都产生相同的行为,但在编译时几乎没有区别。 第一个和第二个很简单所以..从第三个开始 当您使用构造函数时

class C3 {
    constexpr C3() noexcept = default;
    int* ptr;
    int val;
}

通过使用 constexpr 值在任何情况下都不会改变我们也可以使用 cont 并且通过使用 noexcept 我们正在检查它是否 returns 在编译时是否为真理解)

class C6 {
    constexpr C6() noexcept : ptr{nullptr}, val{0} {}
    int* ptr;
    int val;
};

如果我们谈论它,那么我们正在进行成员初始化,这非常快,因为编译时间节省了几毫秒,这对于大型程序来说是巨大的。 我可以说这是一个小差异希望它会很有用。

上面的部分代码是非法的,无法通过编译。我将检查错误并解释错误原因。

class C3 {
    constexpr C3() noexcept = default;
    // A constexpr constructor cannot be defaulted since the default
    // version of the constructor is not constexpr. In this case, you will
    // thus have to always explicitly define a constexpr constructor.
// ...
};

// For C4, on the other hand, defaulting the constexpr will work since
// you gave default (constant) values for all members. The default
// constructor will not have to care about them.

class C5 {
    constexpr C5() noexcept : ptr{nullptr}, val{0} = default;
    // Besides this being invalid syntax, you are implicitly defining
    // a constructor, and then using the default version of it, which
    // does not make sense.
// ...
};

// The rest of the errors are just plain reproduction in a slightly
// amended form.

除此之外,(有效)代码将产生完全相同的 运行时-行为;声明为 constexpr 的构造函数,如果在 constexpr 上下文中使用,将在编译时进行评估(这通常是一件好事,因为它可以节省一些计算量)。一个例子是:

constexpr auto some_c10 = C10{};
// or
constexpr void do_something() { // works for constexpr functions as well! yay!
    auto some_other_c10 = c10{};
}

值得注意的是,some_c10 的非 constexpr 版本将在运行时调用完全相同的 constexpr 版本的构造函数,除非非 [=17] =] 指定版本,而此版本将在编译时评估 constexpr 构造函数。这里的 constexpr-context 显然是使用 constexpr 关键字创建的;省略它将导致非 constexpr 版本,除非聪明的编译器选择声明它 constexpr 无论如何。

此外,第一个class会隐式创建一个平凡的构造函数,也就是说,构造函数不会执行任何操作并保持值未初始化(这里没关系:它会创建一个平凡的构造函数,但由于给定的类型是 classes,它们具有非静态私有成员,这会阻止编译器定义这样的构造函数。尽管将 C1 声明为 struct 会起作用。

除此之外,你发现的各种拼写真的没有区别。它主要只是表达完全相同事物的另一种方式。尽管在几乎所有情况下,函数的外联定义应该是首选解决方案。

class C1 {
    int* ptr;
    int val;
}

编译将声明并定义一个 public 普通 noexcept 默认构造函数。由于微不足道,它不会对成员执行 any 初始化。 用户可以在默认初始化(不会执行任何数据成员初始化)或值初始化(将对数据成员进行零初始化)之间进行选择:

C1 x;        // default-initialized
C1 y = C1(); // value-initialized

一个普通的默认 ctor 会影响对象的生命周期和 class 的 PODness。

如果 C1 是一个 struct(即数据成员是 public),它将是一个聚合:

C1 a {nullptr, 42};
C1 z{};      // aggregate-initialized, but same effects as for y

尽管默认构造函数不是 constexpr,但您可以在常量表达式中创建此 class 的实例:很简单,默认构造函数不会在值初始化中调用(也不会在聚合初始化)。


class C2 {
    int* ptr = nullptr;
    int val = 0;
};

编译器将声明并定义一个public constexprnoexcept 默认构造函数,它根据数据成员的NSDMI(非静态数据成员初始化器,即= x;)。默认初始化和值初始化都将调用默认构造函数并初始化成员。根据 C++14 规则,struct C2 将是一个聚合。


class C3 {
    constexpr C3() noexcept = default;
    int* ptr;
    int val;
};

是非法的,因为编译器声明自己的构造函数(参见 C1)不会是 constexpr。它不是 constexpr 因为它没有初始化所有数据成员。请注意,OP 中所有用户声明的 ctors 都是(隐式)私有的。


class C4 {
    constexpr C4() noexcept = default;
    int* ptr = nullptr;
    int val = 0;
};

C2相同。


class C5 {
    constexpr C5() noexcept : ptr{nullptr}, val{0} = default;
    int* ptr;
    int val;
};

语法上不合法。你不能只默认函数体,你必须默认整个构造函数。


class C6 {
    constexpr C6() noexcept : ptr{nullptr}, val{0} {}
    int* ptr;
    int val;
};

C2 相同的效果,除了这是一个私人 ctor 并且 struct C6 将不再是一个聚合,因为这个 ctor 是用户提供的。


class C7 {
    constexpr C7() noexcept;
    int* ptr;
    int val;
};
constexpr C7::C7() noexcept = default;

非法的原因与 C3 相同。


class C8 {
    constexpr C8() noexcept;
    int* ptr = nullptr;
    int val = 0;
};
constexpr C8::C8() noexcept = default;

由于构造函数在第一次声明时没有默认,这是一个(私有)用户提供的默认构造函数。因此,与 C6.

相同的行为
class C9 {
    constexpr C9() noexcept;
    int* ptr;
    int val;
};
constexpr C9::C9() noexcept : ptr{nullptr}, val{0} = default;

非法的原因与 C3 相同。


class C10 {
    constexpr C10() noexcept;
    int* ptr;
    int val;
};
constexpr C10::C10() noexcept : ptr{nullptr}, val{0} {}

C8constexpr 函数是隐式内联的。