C ++ 11允许在级非静态和非const成员的初始化。 是什么改变了?(C++11 allow

2019-06-21 09:31发布

C ++ 11之前,我们只能执行对整型或枚举类型的静态常量成员在类的初始化。 斯特劳斯讨论了这个在他的C ++ FAQ ,让下面的例子:

class Y {
  const int c3 = 7;           // error: not static
  static int c4 = 7;          // error: not const
  static const float c5 = 7;  // error: not integral
};

而下面的推理:

那么,为什么这些不方便的限制存在? 一类是在头文件通常声明和头文件通常包括成许多翻译单元。 然而,为了避免复杂的连接器规则,C ++要求每个对象都有一个独特的定义。 如果C ++允许类是需要被存储在内存中为对象实体的定义,规则将被打破。

然而,C ++ 11放松这些限制,允许在级非静态成员(§12.6.2/ 8)的初始化:

在一个非委托构造,如果一个给定的非静态数据成员或基类不是由MEM-初始化-ID指定(包括的情况下不存在MEM-初始化列表因为构造不具有构造函数,初始值设定 )与实体不是虚拟基类的抽象类(10.4),那么

  • 如果实体是具有大括号或相等的初始值设定的非静态数据成员,如在8.5中指定的实体被初始化;
  • 否则,如果该实体是一个变体部件(9.5),不进行初始化;
  • 否则,该实体是缺省初始化(8.5)。

第9.4.2节还允许类非const静态成员的初始化,如果他们都标有constexpr符。

所以,发生了什么原因的限制我们在C ++ 03? 难道我们只是简单地接受了“复杂的连接规则”或有别的事情发生变化,使这个更容易实现?

Answer 1:

简短的回答是,他们保持了连接器大致相同,在使编译器仍然比以前更复杂的代价。

即,不是这导致多个定义链接器进行梳理,它仍然只在一个清晰的结果,编译器必须整理出来。

这也导致有些更复杂的规则为程序员保持整理出来为好,但它主要是很简单的,它不是一个大问题。 额外的规则来的,当你有一个成员指定两个不同的初始化:

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

现在,在这一点上处理的是使用什么值的特殊规则来初始化a当您使用非默认的构造函数。 这个问题的答案很简单:如果你使用不指定任何其他值构造函数,那么1234将被用来初始化a -但如果你使用一个构造函数指定其他值,则1234基本上是忽略。

例如:

#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 << "\n" << y;
    return 0;
}

结果:

1234
5678


Answer 2:

我猜想,推理可能的模板被完成之前已被写入。 所有的“复杂的连接规则(S)”必需的类静态成员的初始化后/已经是必要的C ++ 11支持的模板静态成员。

考虑

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 << "\n";
}

编译器的问题是在这三种情况下相同的:在翻译单位应该发出的定义s必要的初始化和代码? 简单的解决办法是到处散发,并让连接器把它清除出来。 这就是为什么,接头已经支持之类的东西__declspec(selectany) 它只是不会有可能实现C ++ 03离不开它。 这就是为什么它是没有必要延长连接。

为了说得更不客气地说:我想在旧标准中给出的理由是完全错误的。


UPDATE

正如卡皮尔指出的,我的第一示例甚至不允许在当前标准(C ++ 14)。 我离开它,无论如何,因为IMO是实现(编译器,链接器)最困难的情况下。 我的观点是:即使这种情况下,是不是比使用模板时发生了什么已经允许例如,任何困难。



Answer 3:

从理论上讲So why do these inconvenient restrictions exist?...的原因是有效的,但它可以相当容易地绕过,这是C ++ 11做什么。

当你一个文件,它只是包括文件和忽略任何初始化。 当你实例化类的成员仅初始化。

换句话说,初始化仍与构造打成平手,只是表示法是不同的,更方便。 如果构造不叫,值不被初始化。

如果构造函数被调用时,值与一流初始化初始化如果存在或构造可以用自己的初始化重写。 初始化的路径基本上是相同的,那就是,通过构造。

这从斯特劳斯自己是显而易见的常见问题在C ++ 11。



文章来源: C++11 allows in-class initialization of non-static and non-const members. What changed?