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

不违反。类的静态成员,即使其生命周期长于主函数,但其仍能保证在任何情况(包括多线程环境)下,在使用对象前先调用构造函数构造对象、获取资源,在程序结束时调用析构函数析构对象、释放资源。所以这就是满足 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++ 直接规定上例为语法错误,禁止在类内初始化静态成员。例外情况: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;
}

friend void g()
{
}

};在上例中,尽管类体中出现了函数体,尽管这也跟 [1] 中所述一样,理论上属不可调和的矛盾,但是 C++还是例外地规定,类内声明的函数体,无论它是成员函数还是非成员 friend 函数,都有一个隐式的 inline 标识。这样,就能允许类的成员函数(friend 非成员函数)的函数体出现在头文件中,被多个源文件包含而不会出现链接错误。但是,要是普通函数,出现了函数体又不加 inline 的话,被放在头文件里又被多个源文件包含,就要出链接问题了。4. C++ 中与模板有关的一切,都是可以放头文件里被多个源文件包含的。普通的模板函数的函数体,哪怕不加 inline,都可以放头文件里。

你可能感兴趣的:(C++ 类内static 需要在类外初始化,是否违反了C++ RAII的原则?)