是否可以依赖函数范围的静态变量来执行程序关闭期间调用的方法?
Can I rely on a function-scoped static variable for a method called during program shutdown?
快速上下文:我在程序关闭时看到错误,这些错误源于全局成员之间的依赖关系(::叹息::,我知道,我知道(。一个全局变量的析构函数可能引用另一个全局变量 - 如果那个全局变量已经被破坏,事情就会变得糟糕。
但这里有一个特殊情况,我只是不知道行为是否定义良好:函数中的静态变量。我是否可以依赖函数即使在程序关闭期间也能始终如一地运行?或者是否有可能销毁静态成员,并且函数无论如何都会运行,而不会创建新成员?
这是一个玩具示例,演示了我感兴趣的内容:
class Logger
{
public:
enum class Severity { DEBUG, INFO, WARNING, ERROR };
void Log(Severity sev, const std::string& msg)
{
LogImpl(FormatMessage(sev, msg));
}
Logger() { Log(Severity::INFO, "Logger active"); }
~Logger() { Log(Severity::INFO, "Logger inactive"); }
protected:
static std::string FormatMessage(Severity sev, const std::string& msg)
{
static const std::map<Severity, std::string> enum2str {
{Severity::DEBUG, "DEBUG"},
{Severity::INFO, "INFO"},
{Severity::WARNING, "WARNING"},
{Severity::ERROR, "ERROR"}
};
// Throws or crashes if enum2str is invalid, or uninitialized:
return "[" + enum2str[sev] + "] " + msg;
}
void LogImpl(const std::string& msg)
{
std::cout << msg << std::endl;
}
};
假设我有一个全局的Logger
实例。Logger::FormatMessage
中的enum2str
映射是一个静态变量,因此在程序关闭期间的某个时刻,它将被销毁。
按照标准,这会导致我的程序在关机时崩溃吗?在关机期间,enum2str
本质上是不可靠的吗?或者对此进行一些处理 - 例如,如果enum2str
在某个时候无效,也许将创建一个新的静态实例?
(我对依赖对象之间的销毁顺序不感兴趣,例如,我声明全局Logger
实例。
没有看到更多程序,一般答案是肯定的。该静态映射的销毁可能会导致程序具有未定义的行为:
[基本.开始.术语]
3 如果构造函数的完成或动态初始化 具有静态存储持续时间的对象强烈发生在 另一个,第二个析构函数的完成是有序的 在第一个析构函数启动之前 [...]
4 如果函数包含静态或线程的块范围对象 已销毁且调用函数的存储持续时间 在销毁具有静态或线程存储的对象期间 持续时间,如果控制流,则程序具有未定义的行为 通过先前销毁的块范围的定义 对象。同样,如果块范围对象,则行为未定义 在其销毁后间接使用(即通过指针(。
通常,静态对象的销毁顺序与初始化顺序相反。因此,假设您有一个在记录器中的映射之前提前初始化的静态对象,并且它在自己的析构函数中记录了一些内容,您将获得未定义的行为。
我对依赖对象之间的破坏顺序不感兴趣
您应该这样做,因为这正是确定在程序关闭期间调用FormatMessage
是否安全的原因。
在关闭期间运行的代码是静态对象的析构函数,以及向atExit
注册的函数。
是否可以依赖函数范围的静态变量来执行程序关闭期间调用的方法?
一般来说,你不能依赖它,但在某些情况下你可以依赖它。
在atExit
中依赖静态对象是安全的,因此在那里调用FormatMessage
是安全的。除非你能保证特定静态对象s
和enum2str
之间的破坏顺序,否则在s
的析构函数中使用FormatMessage
是不安全的。
静态对象保证按其构造的相反顺序被销毁。因此,在销毁构造函数调用FormatMessage
的静态对象子集期间,您可以依赖enum2str
存在,因为在构造函数中调用FormatMessage
可确保在该依赖静态对象完成构造之前构造enum2str
。
无论销毁顺序如何,依赖静态对象都有一个技巧:永远不要破坏依赖者。这可以通过使用静态函数作用域指针来实现,该指针指向动态分配的对象,您有意从不删除该对象。作为一个缺点,这将触发记忆分析仪中的诊断,并可能增加您的教条同事的血压。
- 如何强制从重写方法调用重写的方法基方法?
- C++:使用方法调用析构函数的顺序是什么?
- 派生类调用父类的方法,该方法调用重写的虚拟方法调用错误的方法
- 使用 object 中的方法调用带有 std::bind 和 std::function.target 的 C 样式函数
- 指向类方法调用的指针
- 如何使用 SFINAE 在方法调用中有条件地定义变量?
- 是否有可以处理方法调用依赖关系的设计模式?
- 如何缩短C++中的方法调用?
- 从部分专用模板方法调用模板非静态方法
- 有没有办法禁止派生类中的基类方法调用?
- 为什么这C++只在编译器上编码一个不明确的方法调用Microsoft?
- 从父方法调用子方法
- 如何将子方法调用到父方法
- 虚拟函数在哪里使用 vpointer to vtable 来解析方法调用,非虚拟方法存储在哪里以及如何解析它们?
- 从静态方法调用静态函数指针
- 从同一类中的另一个方法调用方法时出错
- 方法调用意外地像 l 值一样起作用
- 无法从派生的一个方法调用基类方法
- 从类方法调用命名空间中名为 Same 的函数时,重载解析失败
- C 多线程JAVA JNI方法调用