22

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

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

#include <iostream>

using namespace std;

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

    ~Test();

private:
    int count=10;
};

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

代码 2(在构造函数上使用初始化列表初始化):

#include <iostream>

using namespace std;

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

    ~Test();

private:
    int count;
};

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

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

4

5 回答 5

13

成员初始化

在这两种情况下,我们都在谈论成员初始化。请记住,成员按照它们在类中声明的顺序进行初始化。

代码 2:成员初始化器列表

在第二个版本中:

Test() : count(10) {

: count(10) 是构造函数初始化器 ( ctor-initializer ) 并且count(10)成员初始化器,作为成员初始化器列表的一部分。我喜欢将此视为初始化发生的“真实”或主要方式,但它并不能确定初始化的顺序。

代码 1:默认成员初始化器

在第一个版本中:

private:
    int count=10;

count有一个默认成员初始化器。这是后备选项。如果构造函数中不存在任何成员初始化程序,它将被用作成员初始化程序,但在类中,初始化成员的顺序是确定的。

从第12.6.2 节初始化基础和成员,标准的第 10 项:

如果给定的非静态数据成员同时具有大括号或等式初始化器和内存初始化器,则执行内存初始化器指定的初始化,并且非静态数据成员的大括号或等式初始化器是忽略。[示例:给定

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

A(int) 构造函数将简单地将 i 初始化为 arg 的值,并且 i 的大括号或相等初始化器中的副作用不会发生。—结束示例]

要记住的其他一点是,如果您引入非静态数据成员初始化程序,那么结构将不再被视为 C++11 中的聚合,但这已针对 C++14 进行了更新


差异

使用一种形式而不是另一种形式(如果有的话)有什么区别。

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

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

明确表示希望在所有构造函数中使用相同的值。避免重复。避免维护问题。它导致最短和最有效的代码。

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

使用类内成员初始化器可以让编译器为您生成函数。编译器生成的函数可以更有效。

即使我没有编写编译器,我也不会与 Stroustrup、Sutter 以及他们的数百名朋友和同事争论,因此我无法证明它更有效。尽可能使用类内初始化器。

  1. 如果您不熟悉指南,请按照链接查看示例代码和更多说明。
于 2016-04-13T14:21:42.023 回答
5

我能想到的区别是成员初始化器列表默认成员初始化器之前。

通过默认成员初始化程序,它只是包含在成员声明中的大括号或等号初始化程序,如果成员在成员初始化程序列表中省略,则使用该初始化程序。

如果成员具有默认成员初始化器并且还出现在构造函数的成员初始化列表中,则忽略默认成员初始化器。

例如:

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;
};
于 2016-04-13T13:59:47.547 回答
2

代码没有区别。如果您将有多个构造函数重载并且不止一个计数将是 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;
};

成员变量越多,差异越大。

于 2016-04-13T13:50:51.923 回答
1

当您在成员声明旁边进行初始化时,这仅在 C++11 及更高版本中有效,因此如果您在 C++98/03 中,您完全不能这样做。

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

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

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


顺便说一句,没有必要在new这里使用来创建Test. 当人们从其他语言转向该语言时,这是一个常见的错误,我想让你知道。当然,在您的示例之外执行此操作有很多用途。

于 2016-04-13T13:43:32.780 回答