当前位置: 首页 > 知识库问答 >
问题:

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

薛欣德
2023-03-14

在C 11之前,我们只能对整型或枚举类型的静态常量成员执行类内初始化。Stroustrup在他的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指定的(包括由于构造函数没有ctor初始值设定项而没有mem初始值设定项列表的情况),并且实体不是抽象类(10.4)的虚拟基类,则

  • 如果实体是具有大括号或相等初始值设定项的非静态数据成员,则该实体按照8.5中的规定进行初始化

第9.4.2节还允许对非常量静态成员进行类内初始化,如果它们被标记为constexpr。

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

共有3个答案

印曜灿
2023-03-14

理论上那么为什么存在这些不方便的限制呢?...原因是有效的,但它可以很容易地绕过,这正是C 11所做的。

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

换句话说,初始化还是和构造函数绑在一起的,只是表示法不同,比较方便,如果不调用构造函数,就没有初始化值。

如果调用构造函数,则使用类内初始化(如果存在)对值进行初始化,或者构造函数可以使用自己的初始化覆盖该初始化。初始化的路径本质上是相同的,即通过构造函数。

这从C 11上Stroustrup自己的常见问题中可以明显看出。

翁硕
2023-03-14

我猜推理可能是在模板最终确定之前编写的。毕竟,静态成员的类内初始化器所必需的“复杂链接器规则”对于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的定义以及初始化它所需的代码?简单的解决方案是将其发送到任何地方,并让链接器进行分类。这就是为什么链接器已经支持类似于\uu declspec(selectany)的东西。如果没有它,就不可能实现C 03。这就是为什么没有必要扩展链接器。

更坦率地说:我认为旧标准中给出的推理完全错误。

更新

正如Kapil所指出的,我的第一个例子甚至在当前的标准(C 14)中都不被允许。不管怎样,我把它留了下来,因为它是实现(html" target="_blank">编译器、链接器)最困难的情况。我的观点是:即使是这种情况也不会比已经允许的情况更困难,例如在使用模板时。

王才英
2023-03-14

简单的回答是,他们保持链接器不变,但代价是使编译器比以前更复杂。

一、 例如,这不会导致链接器排序多个定义,它仍然只会生成一个定义,编译器必须对其进行排序。

这也会导致程序员需要整理出一些更复杂的规则,但它大多足够简单,所以没什么大不了的。当为一个成员指定了两个不同的初始值设定项时,会出现额外的规则:

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
 类似资料:
  • 本文向大家介绍PHP静态成员变量和非静态成员变量详解,包括了PHP静态成员变量和非静态成员变量详解的使用技巧和注意事项,需要的朋友参考一下 数据成员可以分静态变量、非静态变量两种. 静态成员:静态类中的成员加入static修饰符,即是静态成员.可以直接使用类名+静态成员名访问此静态成员,因为静态成员存在于内存,非静态成员需要实例化才会分配内存,所以静态成员不能访问非静态的成员..因为静态成员存在于

  • 初始化的区别是什么:1)int i=47;或2)int i;{i=47;在哪些情况下我们需要第一个或第二个?

  • 根据JLS: 内部类是不是显式或隐式声明为静态的嵌套类。内部类不能声明静态初始值设定项或成员接口。 但我下面的代码编译成功。 有人能帮我理解这种行为吗

  • 我遇到了下面的Java代码,起初看起来不错,但从未编译过: 下面是IDE :变量USER_ID可能已分配的错误消息。 将值赋值给静态最终变量有问题吗?

  • 本文向大家介绍C ++静态成员变量及其初始化,包括了C ++静态成员变量及其初始化的使用技巧和注意事项,需要的朋友参考一下 静态C ++成员变量是使用static关键字定义的。类中的静态成员变量由所有类对象共享,因为在内存中只有它们的一个副本,而与该类的对象数量无关。 如果没有以任何其他方式初始化静态类成员变量,则在创建类的第一个对象时将其初始化为零。 给出了一个演示静态成员变量及其在C ++中的

  • 问题内容: 我的问题是关于关键字的一种特殊用法。可以使用关键字覆盖不属于任何函数的类中的代码块。例如,以下代码编译: 如果删除关键字,它将抱怨,因为变量是。但是,可以同时删除和关键字并进行编译。 这两种方式都使我感到困惑。我应该如何拥有不属于任何方法的代码段?如何调用它?通常,此用法的目的是什么?或者更好的是,在哪里可以找到有关此文档的文档? 问题答案: 带有修饰符的代码块表示一个类初始化程序。没