第26章 扩展的inline说明符(C++17)

在C++17标准之前,定义类的非常量静态成员变量是一件让人头痛的事情,因为变量的声明和定义必须分开进行,比如:

#include <iostream>
#include <string>

class X {
public:
  static std::string text;
};

std::string X::text{ "hello" };

int main()
{
  X::text += " world";
  std::cout << X::text << std::endl;
}

在这里static std::string text是静态成员变量的声明,std::string X::text{ "hello" }是静态成员变量的定义和初始化。为了保证代码能够顺利地编译,我们必须保证静态成员变量的定义有且只有一份,稍有不慎就会引发错误,比较常见的错误是为了方便将静态成员变量的定义放在头文件中:

#ifndef X_H
#define X_H
class X {
public:
  static std::string text;
};

std::string X::text{ "hello" };
#endif

将上面的代码包含到多个CPP文件中会引发一个链接错误,因为include是单纯的宏替换,所以会存在多份X::text的定义导致链接失败。对于一些字面量类型,比如整型、浮点类型等,这种情况有所缓解,至少对于它们而言常量静态成员变量是可以一边声明一边定义的:

#include <iostream>
#include <string>

class X {
public:
  static const int num{ 5 };
};

int main()
{
  std::cout << X::num << std::endl;
}

不过有得有失,虽然常量性能让它们方便地声明和定义,但却丢失了修改变量的能力。对于std::string这种非字面量类型,这种方法是无能为力的。

为了解决上面这些问题,C++17标准中增强了inline说明符的能力,它允许我们内联定义静态变量,例如:

#include <iostream>
#include <string>

class X {
public:
  inline static std::string text{"hello"};
};

int main()
{
  X::text += " world";
  std::cout << X::text << std::endl;
}

上面的代码可以成功编译和运行,而且即使将类X的定义作为头文件包含在多个CPP中也不会有任何问题。在这种情况下,编译器会在类 X的定义首次出现时对内联静态成员变量进行定义和初始化。

本章介绍的inline说明符的扩展特性解决了C++中定义静态成员变量烦琐且容易出错的问题,它让编译器能够聪明地选择首次出现的变量进行定义和初始化。这种能力也正是inline说明符的提案文档中的第一段话所提到的:“inline说明符可以应用于变量以及函数。声明为inline的变量与函数具有相同的语义:它们一方面可以在多个翻译单元中定义,另一方面又必须在每个使用它们的翻译单元中定义,并且程序的行为就像是同一个变量。”