昨天我问了一个关于单身人士和模板的问题(Meta programming with singleton),这引发了关于单身人士使用的争论。我个人不是他们的粉丝,但对于我的特殊问题,我无法找到替代方案。我想描述我的问题,并希望得到关于我可以创建强大解决方案的反馈。
背景:我正在研究的软件已存在了15年,涵盖了多个exe和dll(适用于Windows);我已经工作了6个月。
我有一个类,Foo在共享库中。 Foo是一个具有非常小的生命周期(~5秒)的对象,可以在任何线程,任何进程和任何时间创建。我现在正在使用新功能扩展Foo,并且需求是一个名为FooInit()的函数必须在执行任何Foo对象之前运行,并在进程退出时运行FooDestroy()。
问题是,Foo对象的创建是任意的 - 代码的任何部分都可以调用:
Foo* foo = new Foo();
Foo的ctor中的boost :: call_once适用于FooInit(),但不能帮助我解决调用FooDestroy()的问题。引用计数Foo没有用,因为在任何时候内存中都可能有[0,n]并且要创建更多,所以当计数变为0时我不能调用FooDestroy()。
我目前的解决方案是在Foo的ctor中创建并使用“FooManager”单例。单例将调用FooInit(),并且在将来的某个时间FooDestroy()最终将被调用。我倾向于这个解决方案,因为它似乎是最安全和最低风险。
感谢任何反馈。
答案 0 :(得分:2)
如果绝对不能多次调用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()
提供了一种方法。虽然单例可以为当前问题提供安全且低风险的解决方案,但这需要付出代价。单身人士的后果可能比其他解决方案产生更多的技术债务,并且可能需要偿还这笔债务以解决未来的问题。