C++ 11成员初始化列表与类内初始化程序?

Ahm*_*Ali 38 c++ initialization c++11

在C++ 11中初始化对象成员变量的这些方法有何不同?还有另外一种方法吗?哪种方式更好(性能)?:

class any {
  public:
    obj s = obj("value");
    any(){}
};
Run Code Online (Sandbox Code Playgroud)

要么

class any {
  public:
    obj s;
    any(): s("value"){}
};
Run Code Online (Sandbox Code Playgroud)

谢谢.

Pio*_*cki 44

不,这些都不一样.

它们之间的区别与适用于直接初始化复制初始化的相同,这是微妙但通常非常混乱.

§12.6.2[class.base.init]:

  1. 表达式列表支撑-INIT列表MEM-初始化用于初始化指定子对象(或者,在一个委派构造,完整的类对象的情况下)按照8.5的初始化规则直接初始化.[...]

  2. 在非委托构造函数中,如果给定的非静态数据成员或基类未由mem-initializer-id指定(包括没有mem-initializer-list的情况,因为构造函数没有ctor-initializer)然后,实体不是抽象类(10.4)的虚基类

    - 如果实体是具有大括号或等于初始化程序的非静态数据成员,则按照8.5中的规定初始化该实体;

§8.5[dcl.init]:

  1. 在表单中发生的初始化

    T x = a;

以及参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和聚合成员初始化(8.5.1)称为复制初始化.

member-initializer-list上初始化非静态数据成员遵循直接初始化的规则,该规则不会创建需要移动/复制的中间临时值(如果没有复制省略而编译),也不是数据成员必须是可复制/可移动的(即使复制被删除).此外,直接初始化引入了显式上下文,而复制初始化是非显式的(如果为初始化选择了构造函数explicit,则程序将无法编译).

换句话说,obj s = obj("value");如果obj声明为:语法将不会编译:

struct obj
{
    obj(std::string) {}
    obj(const obj&) = delete;
};
Run Code Online (Sandbox Code Playgroud)

要么:

struct obj
{
    obj(std::string) {}
    explicit obj(const obj&) {}
};
Run Code Online (Sandbox Code Playgroud)

作为一个更切实的例子,虽然下面不会编译:

struct any
{
   std::atomic<int> a = std::atomic<int>(1); // ill-formed: non-copyable/non-movable
   std::atomic<int> b = 2; // ill-formed: explicit constructor selected
};
Run Code Online (Sandbox Code Playgroud)

这个会:

struct any
{
    std::atomic<int> a;
    std::atomic<int> b{ 2 };
    any() : a(1) {}
};
Run Code Online (Sandbox Code Playgroud)

哪种方式更好(性能)?

随着拷贝省音启用都具有相同的性能.随着拷贝省音被禁用,则在当每个实例化一个额外的复制/移动的构造函数调用拷贝初始化语法使用(即obj s = obj("value");是之一).


还有另外一种方法吗?

大括号或相等的初始化程序语法允许一个进行直接列表初始化,以及:

class any {
public:
    obj s{ "value" };
    any() {}
};
Run Code Online (Sandbox Code Playgroud)

还有其他差异吗?

值得一提的其他一些差异是:

  1. Brace-or-equal-initializer必须与类声明一起驻留在头文件中.
  2. 如果两者合并,则member-initializer-list优先于brace-or-equal-initializer(即,忽略brace-or-equal-initializer).
  3. (仅限C++ 11,直到C++ 14)使用大括号或类似初始化程序的类违反了聚合类型的约束.
  4. 使用大括号或大小写初始化器语法,除了直接列表初始化之外,不可能执行直接初始化.


Ded*_*tor 7

两个例子都是等价的.
虽然只有类型是可复制的或可移动的(自己检查)并且NRVO实际上已经完成(任何中途不错的编译器都会这样做).

虽然如果你有许多构造函数和构造函数链接是不合适的,第一种方法将允许你不要重复自己.

此外,您可以使用该方法定义聚合,其默认值不同于自C++ 14以来(部分)成员的聚合初始化.


Ste*_*mer 5

他们是一样的。

就性能而言,两者都不比另一个更好,并且没有其他方法可以初始化它们。

类内初始化(示例中的第一个)的好处是初始化的顺序是隐式的。在初始化列表中,您必须明确说明顺序 - 如果顺序不正确,编译器将警告无序初始化。

从标准来看:

12.6.2.5
nonstatic data members shall be initialized in the order they were declared 
in the class definition
Run Code Online (Sandbox Code Playgroud)

如果你的列表中的顺序错误,GCC 会抱怨:

main.cpp: In constructor 'C::C()':
main.cpp:51:9: warning: 'C::b' will be initialized after
main.cpp:51:6: warning:   'int C::a'
Run Code Online (Sandbox Code Playgroud)

初始化列表的好处可能是一个品味问题 - 列表是明确的,通常在源文件中。类内是隐式的(可以说),并且通常位于头文件中。

  • 非常感谢,我个人喜欢第一种方法,因为它很熟悉(以前的Java程序员:D),但我担心会有性能差异。 (2认同)