C++ 类内static 需要在类外初始化,是否违反了C++ RAII的原则?

RT
关注者
56
被浏览
15,413

8 个回答

不违反。

类的静态成员,即使其生命周期长于主函数,但其仍能保证在任何情况(包括多线程环境)下,在使用对象前先调用构造函数构造对象、获取资源,在程序结束时调用析构函数析构对象、释放资源。所以这就是满足 RAII 了。


补充

“类的静态成员必须在类外初始化”这一语法是受继承自 C 的编译模型所限。在 C 的经典惯例中,头文件中只放声明(包括函数声明、extern 引导的外部变量声明等),不放实现(包括实现函数的函数体,以及变量的初始化等),而源文件中给出实现。每个源文件构成一个基本的编译单元,各个编译单元之间在编译时没有任何的信息共享。

举个栗子,b.c 想用到 a.c 中的函数 void fun() / 全局变量,它可以在自己的文件开头写下声明语句 void fun(); / extern int x; 但是,更佳的实现方式是 a 模块提供一个头文件 a.h,在里面给出本模块所有对外暴露的函数/全局变量的声明,给 b.c 去 include。但是,如果 b.c 既不是自己写声明语句,也不 include 别的头文件里写的声明语句,而是在自己内部写出 void fun 的函数体,或者是 int x; 或者 int x = 0; 这种定义语句,那么,尽管 b.c 在被编译为 b.o 的过程中不会出编译错误,但是在 b.o 与 a.o 链接时,就会出现符号冲突。

c++ 沿袭了这一经典模型,其虽然多了很多新语法,但仍分为声明语句和实现/初始化语句这两大类。

class Foo
    int a;
    int f();
    static int x;

首先,类的声明就属于声明语句,这上面,类的普通成员属性、方法、静态成员以及整个类体都是声明语句,可被多个源文件所包含(或者可在多个源文件里各声明一次);而

Foo foo;
int Foo::f()
    return 0;
int Foo::x = 0;

这些都属于实现/初始化的语句,只允许出现在一个源文件里。要是多个源文件给出了同一个标识符的声明语句/初始化语句,链接时就会冲突。

又譬如有以下伪代码:

class Foo
    static int x = 0;

尽管类体是声明语句,但是 static int x = 0; 属于初始化语句,两者具有不可调和的矛盾 [1]。故,C++ 直接规定上例为语法错误,禁止在类内初始化静态成员。

例外情况:

  1. C++ 自 C++98 起就允许在类内初始化静态整形编译期常量:
int f()
    return 0;
class Foo
    static const int a = 0; // 唯一例外的正确情况,a 若是 char, short, long 等类型亦可
    static int b = 0; // 错误,b 不是常量
    static const double c = 0; // 错误,c 不是整型
    staitc const int d = f(); // 错误,int f() 函数的运行结果不是编译期常量
                              //(借用 C++11 中的概念,int f() 不是 constexpr 函数)

2. 早在 C++98 的年代,inline 关键字的中心含义就发生了转移。事实上,已不再将其作为内联优化的提示字来看待。它的新的作用就是避免链接时出现符号冲突。如果你在普通函数的函数体前加 inline,那这个函数体就可以放在头文件中,被多个源文件包含了:

inline int fib(int x)
    if (x == 0 || x == 1) {
        return 1;
    return fib(x - 2) + fib(x - 1);

请注意,在这里用 inline 关键字去修饰一个铁定不能做内联处理的递归函数是完全合理的。我还想再次强调下: 请不要再试图用经典教材里的内联的概念去理解 inline 关键字 ,因为 inline 关键字更为重要的语义已经变为以下内容:经 inline 修饰的函数,如果在多个编译单元中出现,而导致发生链接时符号冲突,那么链接器会选择只留下其中一个版本,而丢弃其他副本。

C++17 中还借助 inline 推出了一条新的语法——内联变量:

class Foo
    inline static int x = 0;

有了这一语法,类的静态成员的初始化,甚至于全局变量的初始化,也就都能放在头文件里了。虽然这是一个很小的功能,但是对 head-only 的库来说却是一个非常巨大的帮助。

3. C++98 起还有以下例外情况:

class Foo
    int f()
        return 0;