是静态常量字符串成员变量,始终在使用前初始化

is static const string member variable always initialized before used?

本文关键字:初始化 常量 静态 字符串 成员 变量      更新时间:2023-10-16

在C++中,如果我想定义一些可以在不同的类、函数、文件中使用的非本地 const 字符串,我知道的方法有:

  1. 使用定义指令,例如

    #define STR_VALUE "some_string_value"
    
  2. 常量类成员变量,例如

    class Demo {  
    public:  
      static const std::string ConstStrVal;  
    };  
    // then in cpp  
    std::string Demo::ConstStrVal = "some_string_value";  
    
  3. 常量类成员函数,例如

    class Demo{  
    public:  
      static const std::string GetValue(){return "some_string_value";}  
    };  
    

现在我不清楚的是,如果我们使用第二种方法,变量 ConstStrVal 是否总是在被任何代码实际使用之前初始化为"some_string_value"?由于"静态初始化顺序惨败",我对此感到担忧。如果这个问题是有效的,为什么每个人都使用第二种方法?

哪种是最好的方法,2 还是 3?我知道 #define 指令不尊重范围,大多数人不推荐它。

谢谢!

如果我们使用第二种方法,变量 ConstStrVal 是否总是在被任何代码实际使用之前初始化为"some_string_value"?

这取决于初始化为的值以及初始化顺序。 ConstStrVal有一个全局构造函数。

考虑使用构造函数添加另一个全局对象:

static const std::string ConstStrVal2(ConstStrVal);

顺序不是由语言定义的,ConstStrVal2 的构造函数可以在构造ConstStrVal之前调用。

初始化顺序可能因多种原因而异,但通常由工具链指定。例如,更改链接对象文件的顺序可能会更改图像的初始化顺序,然后错误就会浮出水面。

为什么每个人都使用第二种方法?

许多人出于很好的理由使用其他方法......

哪种是最好的方法,2 还是 3?

数字 3。您还可以避免多个结构,如下所示:

class Demo {
public:  
  static const std::string& GetValue() {
    // this is constructed exactly once, when the function is first called
    static const std::string s("some_string_value");
    return s;
  }  
};  

注意:这种方法仍然能够解决ConstStrVal2(ConstStrVal)中看到的初始化问题。 但是,您可以更好地控制初始化顺序,与具有全局构造函数的对象相比,这是一个更容易可移植解决的问题。

一般来说,我(和许多其他人)更喜欢使用函数而不是变量来返回值,因为函数为将来的增强提供了更大的灵活性。 请记住,花在一个成功的软件项目上的大部分时间是维护和增强代码,而不是首先编写代码。 很难预测今天的常量明天是否可能不是编译时常量。 也许有一天它会从配置文件中读取。

所以我推荐方法 3,因为它可以满足你今天想要的效果,并为未来留出更大的灵活性。

避免将预处理器与 C++一起使用。 另外,为什么你会在一个类中有一个字符串,但在其他类中需要它? 我会重新评估您的类设计,以便更好地封装。 如果你绝对需要这个全局字符串,那么我会考虑添加一个 globals.h/cpp 模块,然后在那里声明/定义字符串为:

const char* const kMyErrorMsg = "This is my error message!";

不要在C++中使用预处理器指令,除非你试图实现一个不可能以任何其他方式实现的神圣目的。

从标准 (3.6.2):

静态存储持续时间 (3.7.1) 的对象应为零初始化 (8.5) 在进行任何其他初始化之前。参考资料 静态存储持续时间和具有静态存储的 POD 类型的对象 持续时间可以用常量表达式 (5.19) 初始化;这是 称为常量初始化。一起,零初始化和 常量初始化称为静态初始化;所有其他 初始化是动态初始化。静态初始化应 在进行任何动态初始化之前执行。动态 对象的初始化可以是有序的,也可以是无序的。 显式专用类模板静态数据的定义 成员已订购初始化。其他类模板静态数据 成员(即隐式或显式实例化的专业化) 具有无序初始化。命名空间中定义的其他对象 作用域具有有序初始化。在单个中定义的对象 翻译单元和有序初始化应初始化 按照翻译单元中的定义顺序。订单 对于具有无序的对象,未指定初始化 初始化和在不同翻译单元中定义的对象。

因此,2 的命运取决于您的变量是静态初始化还是动态初始化。例如,在您的具体示例中,如果您使用const char * Demo::ConstStrVal = "some_string_value";(更好的是const char Demo::ConstStrVal[]如果值在程序中保持不变),您可以确定无论如何它都会被初始化。使用std::string,您无法确定,因为它不是 POD 类型(我对此不太确定,但相当确定)。

第三种方法可以让你确定,贾斯汀答案中的方法确保没有不必要的结构。但请记住,静态方法有一个隐藏的开销,即检查变量是否已在每次调用时初始化。如果你返回一个简单的常量,只返回你的值肯定更快,因为该函数可能会被内联。

综上所述,尝试编写程序,以免依赖静态初始化。静态变量最好被视为一种方便,当您必须处理它们的初始化顺序时,它们不再方便。