首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C++11允许非静态和非常数成员的类内初始化。什么改变了?

C++11允许非静态和非常数成员的类内初始化。什么改变了?
EN

Stack Overflow用户
提问于 2012-12-02 02:35:03
回答 3查看 99.7K关注 0票数 92

在C++11之前,我们只能对整型或枚举类型的静态常量成员执行类内初始化。Stroustrup discusses this in his C++ FAQ,给出了以下示例:

代码语言:javascript
运行
复制
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-initializer-id指定(包括由于构造函数没有ctor-initializer而没有mem-initializer-list的情况),并且实体不是抽象类(10.4)的虚拟基类,则

  • 如果实体是具有大括号或相等初始值设定项的非静态数据成员,则按照8.5中指定的方式初始化实体;
  • 否则,如果实体是可变成员(9.5),则不执行初始化;否则,
  • 将默认初始化实体(8.5)。

第9.4.2节还允许在类内初始化非常数静态成员(如果它们用constexpr说明符标记)。

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

EN

回答 3

Stack Overflow用户

发布于 2012-12-02 02:51:40

简而言之,他们让链接器保持不变,但代价是使编译器比以前更加复杂。

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

这也给程序员带来了一些更复杂的规则,但大部分都很简单,没什么大不了的。当您为单个成员指定了两个不同的初始值设定项时,就会出现额外的规则:

代码语言:javascript
运行
复制
class X { 
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}
};

现在,这里的额外规则处理在使用非默认构造函数时用来初始化a的值。这个问题的答案相当简单:如果您使用的构造函数没有指定任何其他值,那么1234将用于初始化a --但是如果您使用的构造函数指定了其他值,那么1234基本上会被忽略。

例如:

代码语言:javascript
运行
复制
#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;
}

结果:

代码语言:javascript
运行
复制
1234
5678
票数 69
EN

Stack Overflow用户

发布于 2016-01-30 02:29:35

我猜这个推理可能在模板最终定稿之前就已经写好了。毕竟,静态成员的类内初始化器所必需的“复杂链接器规则”对于C++11支持模板的静态成员已经是必要的了。

考虑一下

代码语言:javascript
运行
复制
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。这就是为什么不需要扩展链接器的原因。

更直白地说:我认为旧标准中给出的理由是完全错误的。

更新

正如卡皮尔所指出的,我的第一个例子在当前的标准(C++14)中甚至是不允许的。我把它留在原处,因为它是实现(编译器,链接器)最困难的情况。我的观点是:即使是这种情况也不会比已经允许的情况更难,例如使用模板时。

票数 9
EN

Stack Overflow用户

发布于 2016-06-06 23:56:15

从理论上讲,So why do these inconvenient restrictions exist?...的理由是有效的,但它很容易被绕过,而这正是C++ 11所做的。

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

换句话说,初始化仍然与构造函数捆绑在一起,只是表示法不同,更方便。如果未调用构造函数,则不初始化值。

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

这一点从C++ 11上的Stroustrup自己的FAQ中可见一斑。

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13662441

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档