关于c ++:C ++ 11允许非静态和非const成员的类内初始化。

C++11 allows in-class initialization of non-static and non-const members. What changed?

在C++ 11之前,我们只能对整型或枚举类型的静态const成员进行类初始化。Stroustrup在他的C++ FAQ中讨论了这一点,给出了下面的例子:

1
2
3
4
5
class Y {
  const int c3 = 7;           // error: not static
  static int c4 = 7;          // error: not const
  static const float c5 = 7;  // error: not integral
};

以及以下推理:

So why do these inconvenient restrictions exist? A class is typically declared in a header file and a header file is typically included into many translation units. However, to avoid complicated linker rules, C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects.

然而,C++ 11放宽了这些限制,允许非静态成员的类初始化(P.12.62/2):

In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then

  • if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5;
  • otherwise, if the entity is a variant member (9.5), no initialization is performed;
  • otherwise, the entity is default-initialized (8.5).

第9.4.2节还允许非常量静态成员的类内初始化,前提是它们用constexpr说明符标记。

那么在C++ 03中限制的原因是什么呢?我们只是简单地接受"复杂的链接器规则"还是改变了其他一些使这更容易实现的规则?


简短的回答是,它们保持链接器基本相同,代价是使编译器比以前更加复杂。

也就是说,它仍然只产生一个定义,而不是导致链接器要排序的多个定义,编译器必须将其排序。

它还导致了一些更复杂的规则,程序员也可以保持整理,但它基本上足够简单,这不是一个大问题。如果为单个成员指定了两个不同的初始值设定项,则会出现额外规则:

1
2
3
4
5
6
class X {
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}
};

现在,这一点上的额外规则处理使用非默认构造函数时用来初始化a的值。答案相当简单:如果使用不指定任何其他值的构造函数,那么1234将用于初始化a,但是如果使用指定其他值的构造函数,则基本上忽略1234

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

class X {
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}

    friend std::ostream &operator<<(std::ostream &os, X const &x) {
        return os << x.a;
    }
};

int main() {
    X x;
    X y{5678};

    std::cout << x <<"
"
<< y;
    return 0;
}

结果:

1
2
1234
5678


我猜推理可能是在模板最终确定之前写的。毕竟,静态成员的类初始化器所需的"复杂链接规则"对于C/C++ 11来说已经是必须的,以支持模板的静态成员。

考虑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed,
                                                   // thanks @Kapil for pointing that out

// vs.

template <class T>
struct B { static int s; }

template <class T>
int B<T>::s = ::ComputeSomething();

// or

template <class T>
void Foo()
{
    static int s = ::ComputeSomething();
    s++;
    std::cout << s <<"
"
;
}

编译器的问题在这三种情况下都是一样的:它应该在哪个翻译单元中发出s的定义和初始化它所需的代码?简单的解决方案是在任何地方发出它,并让链接器将其整理出来。这就是为什么链接器已经支持像__declspec(selectany)这样的东西。如果没有它,它就不可能实现C++ 03。这就是为什么不需要扩展链接器的原因。

更直截了当地说:我认为旧标准中给出的推理是完全错误的。

更新

正如Kapil指出的,我的第一个例子在当前的标准(C++ 14)中是不允许的。不管怎样,我还是把它放在了里面,因为IMO是实现(编译器、链接器)最困难的情况。我的观点是:即使是这样的情况也不会比已经允许的更困难,例如在使用模板时。


理论上EDCOX1,6的原因是有效的,但它很容易绕过,这正是C++ 11所做的。

当您包含一个文件时,它只包含该文件并忽略任何初始化。只有在实例化类时,才会初始化成员。

换句话说,初始化仍然与构造函数联系在一起,只是符号不同,而且更方便。如果未调用构造函数,则不会初始化值。

如果调用了构造函数,如果存在,则使用类内初始化初始化初始化值,或者构造函数可以用自己的初始化重写该值。初始化的路径基本上是相同的,即通过构造函数。

这在C++ 11的StruouUp自己的FAQ中是显而易见的。