在我的应用程序中,我有一个代表“系统”的Singleton。此对象首先创建并在最后销毁,所有其他对象应由它管理,这意味着,每个对象将由此系统对象直接或间接创建(在其run
中方法)并且(希望)在系统不可用之前删除所有对象。
问题是对系统对象的可选访问(通过指针)引入了大量非空检查代码。我知道解除引用空指针会导致未定义的行为。使用好老C - assert
我能做的最好吗?
// this is mostly pseudo-code, but should compile if
// there is an appropriate MySys header
#include "MySys.h"
class MySysImplementation: public MySys
{
public:
MySysImplementation();
void run();
// ...
};
/// pointing to system object during runtime
namespace {
MySys* g_sys = NULL;
}
MySys& MySys::Instance()
{
assert(g_sys);
return *g_sys;
}
int main()
{
{
MySysImplementation sys;
g_sys = &sys;
sys.run();
} // <---------- HERE all application-specific objects should be gone
g_sys = NULL; // in real code this RAII-ensured
}
答案 0 :(得分:2)
问题是可选的系统对象引入了许多非空检查代码。
考虑用空对象替换它:
class MySys {
virtual void some_op() = 0;
// other operations here
};
class NullSystem: public MySys {
void some_op() {} // no-op implementations for all APIs
};
class YourSystem: public MySys {
void some_op() { /* your non-optional implementation here */ }
};
我知道解除引用空指针会导致未定义的行为。使用好的旧C-assert我能做的最好?
首先,不要对实例使用全局,也不要使用单例访问点(您的MySys::Instance()
)。单例模式是一种反模式(它主要表示不做什么)。
考虑在main中实例化一个对象并通过引用传递它(也就是说,使用依赖注入而不是单例):
class SomeClientClass
{
SomeClientClass(MySys const & system);
};
int main(int, char**)
{
auto system = std::make_unique<YourSystem>();
auto someClientCode = SomeClientClass(*system);
// ...
// example where you want a SomeClientClass instance without system:
auto client2 = SomeClientClass{ NullSystem{} };
}
答案 1 :(得分:1)
您可能只需执行以下操作:
MySys& MySys::Instance()
{
static MySysImplementation instance;
return instance;
}
答案 2 :(得分:1)
虽然可以在发布模式中删除断言,但最好不要抛出异常。
您的程序可能会因为空指针无异常而崩溃,但这是未定义的行为,可能会产生所有可能的副作用。
异常还具有以下优点:您创建的对象得到清理(堆栈展开),如果您有一部分代码可以正确处理,您还有机会捕获异常并处理它有这个。
你可以扔任何你喜欢的东西,但建议抛出任何来自std::exception
的东西。 std::runtime_error
在这里似乎很好。或者自己得出一些东西。
有一点需要注意:
因为您正在处理全局对象(单例也是全局对象),所以应该避免在析构函数中使用单例。当抛出异常而你是“堆栈展开”并且其中一个对象析构函数会抛出第二个异常时,程序将立即调用std::terminate
。如果你终止这可能不是问题,但是如果你想在某个地方捕获异常,或者你依赖堆栈展开,这可能会导致问题。
答案 3 :(得分:1)
使用好的旧C-assert我能做的最好?
这是一个选择。它是否最好取决于使用空指针时程序需要做什么。
断言有两个方面:
有条件地禁用它,具体取决于NDEBUG
宏 - 通常在发布版本中启用。如果您想无条件地检测空指针,if
语句是更好的选择。
它调用std::abort
来终止进程。如果能够以更优雅的方式解决问题,那么断言不是您的最佳选择。相反,您可以抛出异常,并在可以处理它的调用堆栈中将其捕获更高。
如果无论如何都无法有意义地处理异常,并且如果您希望避免发布版本中的检查的运行时成本,那么断言确实是最好的。
但是,如果可能,您可以通过使用静态实例来保证对象确实存在。请考虑以下事项:
MySys& MySys::Instance()
{
static MySysImplementation sys;
return sys;
}
此sys
保证在任何对象之前和之后存在,在其构造函数中调用MySys::Instance
- 或者其结构保证在MySysImplementation
构造之后。< / p>