如何延迟 C++ 中静态对象(记录器)的销毁?
How to delay destruction of static object (logger) in C++?
我得到了一个 class,它将应用程序的记录器保存为 unique_ptr。可以通过静态函数设置记录器。此外,显然可以记录消息。我省略了任何线程同步(互斥锁)以使事情变得更容易。
class LoggerWrapper {
public:
static void SetLogger(std::unique_ptr<logger::ILogger> new_logger);
static void Log(const std::string& message);
private:
static std::unique_ptr<logger::ILogger> logger_;
};
void LoggerWrapper::Log(const std::string& message) {
if (!logger_) {
// cannot log
} else {
logger_->OnLogEvent(message);
}
}
void LoggerWrapper::SetLogger(std::unique_ptr<logger::ILogger> new_logger) {
logger_ = std::move(new_logger);
}
我的问题是:unique_ptr 在应用程序中的其他一些 class 之前被破坏。 例如Class Foo
的 DTOR 想要记录一些东西,unique_ptr 可能已经被销毁了(目前就是这种情况)。这会导致 ILogger
实现被破坏,导致无法输出日志。
有没有人知道如何轻松解决这个问题? 我需要以某种方式“延迟”静态 unique_ptr 的破坏。我也尝试将其更改为 shared_ptr,但这只会导致 SIGABRT 出现“调用纯虚拟方法”错误。
提前致谢!
编辑:创建了一个与我的经验相矛盾的最小工作示例。在这种情况下,静态记录器比 Foo
class.
EDIT2:我的应用程序使用 exit
。这似乎改变了破坏的顺序。
EDIT3:exit
不会 破坏本地对象。
/******************************************************************************
Online C++ Compiler.
Code, Compile, Run and Debug C++ program online.
Write your code in this editor and press "Run" button to compile and execute it.
*******************************************************************************/
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class ILogger {
public:
ILogger() {
std::cout << "ILogger CTOR" << std::endl;
}
~ILogger() {
std::cout << "ILogger DTOR" << std::endl;
}
virtual void OnLogEvent(const std::string& log_message) {
std::cout << "OnLogEvent: " << log_message << std::endl;
}
};
class LoggerWrapper {
public:
static void SetLogger(std::unique_ptr<ILogger> new_logger) {
logger_ = std::move(new_logger);
}
static void Log(const std::string& message) {
if (!logger_) {
// cannot log
} else {
logger_->OnLogEvent(message);
}
};
private:
static std::unique_ptr<ILogger> logger_;
};
class Foo {
public:
Foo(const std::string& name) : name_{name} {
LoggerWrapper::Log(name_ + ": CTOR");
}
~Foo() {
LoggerWrapper::Log(name_ + ": DTOR");
}
private:
std::string name_;
};
// declaring logger_ first causes it to be deleted AFTER foo
std::unique_ptr<ILogger> LoggerWrapper::logger_;
std::unique_ptr<Foo> foo;
int main()
{
LoggerWrapper::SetLogger(std::make_unique<ILogger>());
foo = std::make_unique<Foo>("Global FOO");
// local variables do NOT get destroyed when calling exit!
auto foo_local = Foo("Local FOO");
exit(1);
}
这很简单。
首先你不使用全局静态对象(你不应该那样使用全局状态)。您使用函数静态对象,因此您可以控制 creation/destruction.
的顺序
所以改变这个:
std::unique_ptr<ILogger> LoggerWrapper::logger_;
std::unique_ptr<Foo> foo;
进入:
class GlobalLogger
{
public:
ILogger& getLogger() {
static ILogger logger; // if you must use unique_ptr you can do that here
return logger; // But much simpler to use a normal object.
}
};
class GlobalFoo
{
public:
Foo& getFoo() {
// If there is a chance that foo is going to
// use global logger in its destructor
// then it should simply call `GlobalLogger::getLogger()`
// in the constructor of Foo. You then
// guarantee the order of creation and thus destruction.
// Alternatively, you can call it here in thus
// function just before the declaration of foo.
static Foo foo;
return foo;
}
};
// Where you were using `logger_` use `GlobalLogger::getLogger()`
// Where you were using `foo` use `GlobalFoo::getFoo()`
如果我们使用您的原始代码作为起点,我们可以这样做:
#include <iostream>
#include <memory>
#include <string>
// Please don't do this.
// This is the number one worst practice.
//
using namespace std;
class ILogger {
public:
ILogger() {
std::cout << "ILogger CTOR" << std::endl;
}
~ILogger() {
std::cout << "ILogger DTOR" << std::endl;
}
virtual void OnLogEvent(const std::string& log_message) {
std::cout << "OnLogEvent: " << log_message << std::endl;
}
};
class LoggerWrapper
{
// Here store the logger
// as a static member of this private function.
// The the SetLogger() Log() functions get this reference.
static std::unique_ptr<ILogger>& getLogReference() {
static std::unique_ptr<ILogger> logger;
return logger;
}
public:
static void SetLogger(std::unique_ptr<ILogger> new_logger) {
// Save the new reference.
getLogReference() = std::move(new_logger);
}
// Use the logger if it has been set.
static void Log(const std::string& message) {
std::unique_ptr<ILogger>& logger_ = getLogReference();
if (!logger_) {
// cannot log
} else {
logger_->OnLogEvent(message);
}
};
};
class Foo {
public:
Foo(const std::string& name) : name_{name} {
// This calls Log()
// Which calls getLogReference()
// Which forces the creation of the function static
// variable logger so it is created before this
// object is fully initialized (if it has not already
// been created).
//
// This means this object was created after the logger
LoggerWrapper::Log(name_ + ": CTOR");
}
~Foo() {
// Because the Log() function was called in the
// constructor we know the loger was fully constructed first
// thus this object will be destroyed first
// so the logger object is guaranteed to be
// available in this objects destructor
// so it is safe to use.
LoggerWrapper::Log(name_ + ": DTOR");
}
private:
std::string name_;
};
std::unique_ptr<Foo>& globalFoo() {
// foo may destroy an object created later
// that has a destructor that calls LoggerWrapper::Log()
// So we need to call the Log function here before foo
// is created.
LoggerWrapper::Log("Initializing Global foo");
// Note: Unless somebody else has explicitly called SetLogger()
// the above line is unlikely to log anything as the logger
// will be null at this point.
static std::unique_ptr<Foo> foo;
return foo;
}
int main()
{
LoggerWrapper::SetLogger(std::make_unique<ILogger>());
globalFoo() = std::make_unique<Foo>("Global FOO");
// local variables do NOT get destroyed when calling exit!
auto foo_local = Foo("Local FOO");
exit(1);
}
静态(全局)对象的创建和销毁顺序未定义。这给您留下了几个选择。
- 不要使用全局对象,只使用原始指针,您可以按正确的顺序销毁自己。
- 使用指向您永远不会破坏的记录器的指针。这在技术上是内存泄漏,但内核会在您的应用程序退出时进行清理。
- 为您的记录器使用
shared_ptr
。每个使用记录器的对象都会得到一个 shared_ptr
,记录器将在最后一个对象被销毁后被清理。
我得到了一个 class,它将应用程序的记录器保存为 unique_ptr。可以通过静态函数设置记录器。此外,显然可以记录消息。我省略了任何线程同步(互斥锁)以使事情变得更容易。
class LoggerWrapper {
public:
static void SetLogger(std::unique_ptr<logger::ILogger> new_logger);
static void Log(const std::string& message);
private:
static std::unique_ptr<logger::ILogger> logger_;
};
void LoggerWrapper::Log(const std::string& message) {
if (!logger_) {
// cannot log
} else {
logger_->OnLogEvent(message);
}
}
void LoggerWrapper::SetLogger(std::unique_ptr<logger::ILogger> new_logger) {
logger_ = std::move(new_logger);
}
我的问题是:unique_ptr 在应用程序中的其他一些 class 之前被破坏。 例如Class Foo
的 DTOR 想要记录一些东西,unique_ptr 可能已经被销毁了(目前就是这种情况)。这会导致 ILogger
实现被破坏,导致无法输出日志。
有没有人知道如何轻松解决这个问题? 我需要以某种方式“延迟”静态 unique_ptr 的破坏。我也尝试将其更改为 shared_ptr,但这只会导致 SIGABRT 出现“调用纯虚拟方法”错误。
提前致谢!
编辑:创建了一个与我的经验相矛盾的最小工作示例。在这种情况下,静态记录器比 Foo
class.
EDIT2:我的应用程序使用 exit
。这似乎改变了破坏的顺序。
EDIT3:exit
不会 破坏本地对象。
/******************************************************************************
Online C++ Compiler.
Code, Compile, Run and Debug C++ program online.
Write your code in this editor and press "Run" button to compile and execute it.
*******************************************************************************/
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class ILogger {
public:
ILogger() {
std::cout << "ILogger CTOR" << std::endl;
}
~ILogger() {
std::cout << "ILogger DTOR" << std::endl;
}
virtual void OnLogEvent(const std::string& log_message) {
std::cout << "OnLogEvent: " << log_message << std::endl;
}
};
class LoggerWrapper {
public:
static void SetLogger(std::unique_ptr<ILogger> new_logger) {
logger_ = std::move(new_logger);
}
static void Log(const std::string& message) {
if (!logger_) {
// cannot log
} else {
logger_->OnLogEvent(message);
}
};
private:
static std::unique_ptr<ILogger> logger_;
};
class Foo {
public:
Foo(const std::string& name) : name_{name} {
LoggerWrapper::Log(name_ + ": CTOR");
}
~Foo() {
LoggerWrapper::Log(name_ + ": DTOR");
}
private:
std::string name_;
};
// declaring logger_ first causes it to be deleted AFTER foo
std::unique_ptr<ILogger> LoggerWrapper::logger_;
std::unique_ptr<Foo> foo;
int main()
{
LoggerWrapper::SetLogger(std::make_unique<ILogger>());
foo = std::make_unique<Foo>("Global FOO");
// local variables do NOT get destroyed when calling exit!
auto foo_local = Foo("Local FOO");
exit(1);
}
这很简单。
首先你不使用全局静态对象(你不应该那样使用全局状态)。您使用函数静态对象,因此您可以控制 creation/destruction.
的顺序所以改变这个:
std::unique_ptr<ILogger> LoggerWrapper::logger_;
std::unique_ptr<Foo> foo;
进入:
class GlobalLogger
{
public:
ILogger& getLogger() {
static ILogger logger; // if you must use unique_ptr you can do that here
return logger; // But much simpler to use a normal object.
}
};
class GlobalFoo
{
public:
Foo& getFoo() {
// If there is a chance that foo is going to
// use global logger in its destructor
// then it should simply call `GlobalLogger::getLogger()`
// in the constructor of Foo. You then
// guarantee the order of creation and thus destruction.
// Alternatively, you can call it here in thus
// function just before the declaration of foo.
static Foo foo;
return foo;
}
};
// Where you were using `logger_` use `GlobalLogger::getLogger()`
// Where you were using `foo` use `GlobalFoo::getFoo()`
如果我们使用您的原始代码作为起点,我们可以这样做:
#include <iostream>
#include <memory>
#include <string>
// Please don't do this.
// This is the number one worst practice.
//
using namespace std;
class ILogger {
public:
ILogger() {
std::cout << "ILogger CTOR" << std::endl;
}
~ILogger() {
std::cout << "ILogger DTOR" << std::endl;
}
virtual void OnLogEvent(const std::string& log_message) {
std::cout << "OnLogEvent: " << log_message << std::endl;
}
};
class LoggerWrapper
{
// Here store the logger
// as a static member of this private function.
// The the SetLogger() Log() functions get this reference.
static std::unique_ptr<ILogger>& getLogReference() {
static std::unique_ptr<ILogger> logger;
return logger;
}
public:
static void SetLogger(std::unique_ptr<ILogger> new_logger) {
// Save the new reference.
getLogReference() = std::move(new_logger);
}
// Use the logger if it has been set.
static void Log(const std::string& message) {
std::unique_ptr<ILogger>& logger_ = getLogReference();
if (!logger_) {
// cannot log
} else {
logger_->OnLogEvent(message);
}
};
};
class Foo {
public:
Foo(const std::string& name) : name_{name} {
// This calls Log()
// Which calls getLogReference()
// Which forces the creation of the function static
// variable logger so it is created before this
// object is fully initialized (if it has not already
// been created).
//
// This means this object was created after the logger
LoggerWrapper::Log(name_ + ": CTOR");
}
~Foo() {
// Because the Log() function was called in the
// constructor we know the loger was fully constructed first
// thus this object will be destroyed first
// so the logger object is guaranteed to be
// available in this objects destructor
// so it is safe to use.
LoggerWrapper::Log(name_ + ": DTOR");
}
private:
std::string name_;
};
std::unique_ptr<Foo>& globalFoo() {
// foo may destroy an object created later
// that has a destructor that calls LoggerWrapper::Log()
// So we need to call the Log function here before foo
// is created.
LoggerWrapper::Log("Initializing Global foo");
// Note: Unless somebody else has explicitly called SetLogger()
// the above line is unlikely to log anything as the logger
// will be null at this point.
static std::unique_ptr<Foo> foo;
return foo;
}
int main()
{
LoggerWrapper::SetLogger(std::make_unique<ILogger>());
globalFoo() = std::make_unique<Foo>("Global FOO");
// local variables do NOT get destroyed when calling exit!
auto foo_local = Foo("Local FOO");
exit(1);
}
静态(全局)对象的创建和销毁顺序未定义。这给您留下了几个选择。
- 不要使用全局对象,只使用原始指针,您可以按正确的顺序销毁自己。
- 使用指向您永远不会破坏的记录器的指针。这在技术上是内存泄漏,但内核会在您的应用程序退出时进行清理。
- 为您的记录器使用
shared_ptr
。每个使用记录器的对象都会得到一个shared_ptr
,记录器将在最后一个对象被销毁后被清理。