Singleton替代任意对象

时间:2014-01-16 13:44:15

标签: c++ boost

昨天我问了一个关于单身人士和模板的问题(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()最终将被调用。我倾向于这个解决方案,因为它似乎是最安全和最低风险。

感谢任何反馈。

1 个答案:

答案 0 :(得分:2)

如果绝对不能多次调用FooInit()FooDestroy(),并且你与一个忍不住用脚射击自己的受虐狂程序员一起工作,那么功能自己需要被写成幂等的,FooInit()注册FooDestroy()以便在程序终止时通过std::atexit()调用。

另一方面,如果FooInit()FooDestroy()无法修改,而您的同事仍然双脚,那么有一些替代方案。在深入研究它们之前,让我们简要地研究一下针对单身人士的一些争论:

  • 这违反了single responsibility principleFooManager负责通过RAII调度构建FooInit()FooDestroy()。在将其设为单身时,FooManager将负责控制其创建和生命周期。
  • 他们带着状态。单元测试可能变得困难,因为一个单元测试可能会影响不同的单元测试。
  • 它隐藏了依赖关系,因为可以全局访问单例。

一种解决方案是使用dependency injection。使用这种方法,Foo的构造函数将被修改为接受FooManagerFooManager将:

  • 提供幂等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()提供了一种方法。虽然单例可以为当前问题提供安全且低风险的解决方案,但这需要付出代价。单身人士的后果可能比其他解决方案产生更多的技术债务,并且可能需要偿还这笔债务以解决未来的问题。