非静态数据成员的成员初始值设定项列表和默认成员初始值设定项之间有什么区别?

What's the differences between member initializer list and default member initializer on non-static data member?

我想了解使用一种形式与使用另一种形式(如果有)的区别是什么。

Code 1(直接在变量上初始化):

#include <iostream>

using namespace std;

class Test 
{
public:
    Test() {
        cout<< count;
    }

    ~Test();

private:
    int count=10;
};

int main()
{
    Test* test = new Test();
}

Code 2(在构造函数上使用初始化列表进行初始化):

#include <iostream>

using namespace std;

class Test 
{
public:
    Test() : count(10) {
        cout<< count;
    }

    ~Test();

private:
    int count;
};

int main()
{
    Test* test = new Test();
}

语义上有什么区别吗,还是只是语法上的区别?

当你在成员声明旁边初始化时,这仅在 C++11 之后有效,所以如果你在 C++98/03 中,你完全不能这样做。

如果该值永远不会改变,您可以选择将其设为 constexpr static,然后编译器将被要求不为该值使用任何额外的存储空间(只要您不定义它) 并在使用该值的任何地方立即使用常量传播。

使用 by-declaration 语法的一个缺点是它必须在 header 中,这将导致每次您重新编译包含 header 的所有翻译单元想改变它的价值。如果这需要很长时间,那可能是不可接受的。

另一个区别是使用成员初始化列表可以更改每个构造函数的值,而使用 by-declaration 版本只允许您为所有构造函数指定一个值(尽管您可以覆盖这个值。 .. 但我个人会避免这种情况,因为它可能会变得非常混乱!)。


顺便说一句,这里不需要使用 new 来创建 Test 的实例。当人们从其他语言转到该语言时,这是一个常见的错误,我想让你知道。在您的示例之外,这样做当然有很多用途。

代码没有区别。如果您将有多个构造函数重载并且超过一个计数将是 10,那么差异就会出现。对于第一个版本,您需要编写的内容会更少。

class Test 
{
public:
    Test() = default;
    Test(int b) : b(b) {} // a = 1, c = 3

    ~Test();

private:
    int a = 1;
    int b = 2;
    int c = 3;
};

与上面代码看起来像这样的第二个版本相反:

class Test 
{
public:
    Test() : a(1), b(2), c(3) {}
    Test(int b) : a(1), b(b), c(3) {}

    ~Test();

private:
    int a;
    int b;
    int c;
};

成员变量越多差异越大

我能想到的区别就是member initializer list is prior to default member initializer.

Through a default member initializer, which is simply a brace or equals initializer included in the member declaration, which is used if the member is omitted in the member initializer list.

If a member has a default member initializer and also appears in the member initialization list in a constructor, the default member initializer is ignored.

例如:

class Test 
{
public:
    Test() {}  // count will be 10 since it's omitted in the member initializer list
    Test(int c) : count(c) {} // count's value will be c, the default member initializer is ignored. 
private:
    int count = 10;
};

会员初始化

在这两种情况下,我们都在谈论 member initialization。 请记住,成员是在 class 中的 sequence in which they are declared 中初始化的。

代码 2:成员初始化列表

在第二个版本中:

Test() : count(10) {

: count(10) 是构造函数初始值设定项 (ctor-initializer),count(10) 成员初始值设定项 作为成员初始值设定项列表的一部分。我喜欢将其视为初始化发生的 'real' 或主要方式,但它并不能确定初始化的顺序。

代码 1:默认成员初始值设定项

在第一个版本中:

private:
    int count=10;

count 有一个 默认成员初始化程序 。这是后备选项。如果 none 存在于构造函数中,它将用作 成员初始值设定项 ,但在 class 中确定了成员初始化顺序。

来自部分 12.6.2 初始化基和成员,标准的第 10 项:

If a given non-static data member has both a brace-or-equal-initializer and a mem-initializer, the initialization specified by the mem-initializer is performed, and the non-static data member’s brace-or-equal-initializer is ignored. [ Example: Given

struct A {
int i = / some integer expression with side effects / ;
A(int arg) : i(arg) { }
// ...
};

the A(int) constructor will simply initialize i to the value of arg, and the side effects in i’s brace-or-equalinitializer will not take place. —end example ]

还有一点要记住,如果引入非静态数据成员初始值设定项,则结构将不再被视为 C++11 中的聚合,但这一直是 updated for C++14


差异

what's the differences of using one form rather than the other (if any).

  • 区别在于两个选项的优先级。直接指定的构造函数初始值设定项具有优先权。在这两种情况下,我们最终都会通过不同的路径得到一个成员初始值设定项。
  • 最好使用默认的成员初始化器,因为
    • 然后编译器可以使用该信息为您生成构造函数的初始化列表,并且它可能能够进行优化。
    • 您可以在一处按顺序查看所有默认值。
    • 它减少了重复。然后,您只能将异常放在手动指定的成员初始值设定项列表中。

在 C++ 核心指南中(见下面的注释 1),Guideline C.48 推荐第一种方法(in-class 初始化器。)提供的推理是:

Makes it explicit that the same value is expected to be used in all constructors. Avoids repetition. Avoids maintenance problems. It leads to the shortest and most efficient code.

事实上,如果你的构造函数除了初始化成员变量什么都不做,就像你的问题一样,那么 Guideline C.45 更坚定,说肯定要使用 in-class 初始化器。它解释说

Using in-class member initializers lets the compiler generate the function for you. The compiler-generated function can be more efficient.

即使我没有编写编译器,我也不会与 Stroustrup、Sutter 和他们的数百位朋友和同事争论,所以我无法证明它更高效。尽可能使用 in-class 初始值设定项。

  1. 如果您不熟悉指南,请点击链接查看示例代码和更多说明。