昨天我问了一个有关单例和模板(Meta programming with singleton)的问题,这引发了有关单例使用的广泛争论。我个人不喜欢它们,但是对于我的特殊问题,我找不到其他选择。我想描述我的问题,并希望就如何创建可靠的解决方案提供反馈。
背景:我正在使用的软件已经存在15年了,它涵盖了多个exe和dll(适用于Windows);我已经做了6个月了。
我有一个类,Foo在共享库中。 Foo是一个寿命非常短(约5秒)的对象,可以在任何线程,任何进程以及任何时间创建。我现在用新功能扩展Foo,并且要求在执行任何Foo对象之前必须运行名为FooInit()的函数,并在进程退出时运行FooDestroy()。
问题是,创建Foo对象是任意的-代码的任何部分都可以并且确实可以调用:
Foo* foo = new Foo();
Foo的ctor中的boost::call_once适用于FooInit(),但是并不能帮助我解决调用FooDestroy()的问题。引用计数Foo并没有帮助,因为在任何时候内存中可能都有[0,n]并有更多要创建的内容,因此当计数变为0时,我无法调用FooDestroy()。
我当前的解决方案是在Foo的ctor中创建和使用“FooManager”单例。单例将调用FooInit(),并在将来的某个时候最终调用FooDestroy()。我倾向于此解决方案,因为它似乎是最安全,风险最低的解决方案。
任何反馈表示赞赏。
最佳答案
如果绝对不能多次调用FooInit()
和FooDestroy()
是绝对关键的,并且您与受虐狂程序员合作,他们不得不忍受自己的脚步,则需要使用FooInit()
将函数本身写为等幂的注册FooDestroy()
以在程序终止时通过std::atexit()
调用。
另一方面,如果无法修改FooInit()
和FooDestroy()
,并且您的同事仍然双脚,则有一些替代方案。在深入研究它们之前,让我们简要检查一些经常针对单例的论点:
FooManager
负责通过RAII调用结构上的FooInit()
和销毁上的FooDestroy()
。将其设置为单例后,FooManager
将承担控制其创建和生命周期的额外责任。 一种解决方案是使用dependency injection。通过这种方法,
Foo
的构造函数将被修改为接受FooManager
,而FooManager
将:init()
方法,该方法仅调用一次FooInit()
。 FooDestroy()
。 依赖性变得很明显,并且
FooManager
的生存期控制了FooDestroy()
的调用时间。为了使示例简单,我选择不介绍线程安全性,但这是一个基本示例,其中通过使用作用域管理FooManager
的生存期来重置单元测试之间的状态:#include <iostream>
#include <boost/noncopyable.hpp>
// Legacy functions.
void FooInit() { std::cout << "FooInit()" << std::endl; }
void FooDestroy() { std::cout << "FooDestroy()" << std::endl; }
/// @brief FooManager is only responsible for invoking FooInit()
/// and FooDestroy().
class FooManager:
private boost::noncopyable
{
public:
FooManager()
: initialized_(false)
{}
void init()
{
if (initialized_) return; // no-op
FooInit();
initialized_ = true;
}
~FooManager()
{
if (initialized_)
FooDestroy();
}
private:
bool initialized_;
};
/// @brief Mockup Foo type.
class Foo
{
public:
explicit Foo(FooManager& manager)
{
manager.init();
std::cout << "Foo()" << std::endl;
}
~Foo()
{
std::cout << "~Foo()" << std::endl;
}
};
int main()
{
// Some unit test that creates Foo objects.
std::cout << "Unit Test 1" << std::endl;
{
FooManager manager;
Foo f1(manager);
Foo f2(manager);
}
// State is not carried between unit test.
// Some other unit test that creates Foo objects.
std::cout << "Unit Test 2" << std::endl;
{
FooManager manager;
Foo f3(manager);
}
}
产生以下输出:
Unit Test 1
FooInit()
Foo()
Foo()
~Foo()
~Foo()
FooDestroy()
Unit Test 2
FooInit()
Foo()
~Foo()
FooDestroy()
如果修改
Foo
的构造并控制FooManager
的生存期以及如何传递它会带来太大的风险,那么折衷办法可能是通过全局隐藏依赖项。但是,为了划分职责并避免携带状态,可以通过其他类型(例如智能指针)来管理全局可用的FooManager
的生存期。在下面的代码中:FooManager
负责在构造时调用FooInit()
,在破坏时调用FooDestroy()
。 FooManager
的创建通过辅助功能进行管理。 FooManager
的生存期由全局智能指针管理。 #include <iostream>
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
// Legacy functions.
void FooInit() { std::cout << "FooInit()" << std::endl; }
void FooDestroy() { std::cout << "FooDestroy()" << std::endl; }
namespace detail {
/// @brief FooManager is only responsible for invoking FooInit()
/// and FooDestroy().
class FooManager
: private boost::noncopyable
{
public:
FooManager() { FooInit(); }
~FooManager() { FooDestroy(); }
};
/// @brief manager_ is responsible for the life of FooManager.
boost::scoped_ptr<FooManager> manager;
/// @brief Initialize Foo.
void init_foo()
{
if (manager) return; // no-op
manager.reset(new FooManager());
}
/// @brief Reset state, allowing init_foo() to run.
void reset_foo()
{
manager.reset();
}
} // namespace detail
/// @brief Mockup Foo type.
class Foo
{
public:
Foo()
{
detail::init_foo();
std::cout << "Foo()" << std::endl;
}
~Foo()
{
std::cout << "~Foo()" << std::endl;
}
};
int main()
{
// Some unit test that creates Foo objects.
std::cout << "Unit Test 1" << std::endl;
{
Foo f1;
Foo f2;
}
// The previous unit test should not pollute other unit test.
detail::reset_foo();
// Some other unit test that creates Foo objects.
std::cout << "Unit Test 2" << std::endl;
{
Foo f3;
}
}
产生与第一个示例相同的输出。
综上所述,单例和其他解决方案都不能阻止
FooInit()
多次调用,但是它们都提供了一种在程序终止时调用FooDestroy()
的方法。虽然单例可以为当前问题提供安全且低风险的解决方案,但这样做是有代价的。单例的后果可能会比其他解决方案产生更多的技术债务,并且可能需要偿还该债务以解决 future 的问题。关于c++ - 任意对象的单例替代,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21163490/