C++ 任意对象的单例替换

C++ 任意对象的单例替换,c++,boost,C++,Boost,昨天我问了一个关于单例和模板()的问题,这引发了一场关于单例使用的争论。我个人不是他们的粉丝,但对于我的特殊问题,我想不出其他的选择。我想描述一下我的问题,并希望得到关于如何创建一个健壮的解决方案的反馈 背景:我正在开发的软件已经存在15年了,它跨越了多个exe和dll(它是针对Windows的);我已经做了6个月了 我有一个类,Foo,在一个共享库中。Foo是一个生命周期很短(约5秒)的对象,可以在任何线程、任何进程和任何时间创建。我现在用新的功能扩展Foo,要求在执行任何Foo对象之前必须运

昨天我问了一个关于单例和模板()的问题,这引发了一场关于单例使用的争论。我个人不是他们的粉丝,但对于我的特殊问题,我想不出其他的选择。我想描述一下我的问题,并希望得到关于如何创建一个健壮的解决方案的反馈

背景:我正在开发的软件已经存在15年了,它跨越了多个exe和dll(它是针对Windows的);我已经做了6个月了

我有一个类,Foo,在一个共享库中。Foo是一个生命周期很短(约5秒)的对象,可以在任何线程、任何进程和任何时间创建。我现在用新的功能扩展Foo,要求在执行任何Foo对象之前必须运行一个名为FooInit()的函数,在进程退出时必须运行FooDestroy()

问题是,Foo对象的创建是任意的-代码的任何部分都可以调用:

Foo* foo = new Foo();
boost::在Foo的ctor中调用_一次适用于FooInit(),但不能帮助我解决调用foodistroy()的问题。引用counting Foo没有任何帮助,因为内存中随时都可能有[0,n]需要创建更多,所以当计数达到0时,我无法调用foodistroy()

我目前的解决方案是在Foo的ctor中创建并使用一个“FooManager”单例。单例将调用FooInit(),在将来的某个时候,最终将调用foodstroy()。我倾向于这个解决方案,因为它看起来是最安全、风险最低的


感谢您的反馈。

如果绝对重要的是
FooInit()
FooDestroy()
不能被多次调用,并且您与一个受虐狂程序员一起工作,他会情不自禁地射中自己的脚,那么函数本身就需要编写为幂等函数,使用
FooInit()
注册
foodistroy()
在程序终止时通过
std::atexit()
调用

另一方面,如果不能修改
FooInit()
foodistroy()
,并且您的同事仍然有两条腿,那么有几种选择。在深入研究这些问题之前,让我们先简单地分析一下通常反对单身人士的一些论点:

  • 这违反了法律
    FooManager
    负责在构造时调用
    FooInit()
    ,在销毁时通过调用
    foodstroy()
    。当它成为单例时,
    FooManager
    将承担控制其创建和生命周期的额外责任
  • 它们代表国家。单元测试可能会变得困难,因为一个单元测试可能会影响另一个单元测试
  • 它隐藏了依赖项,因为可以全局访问单例
一个解决方案是使用。使用这种方法,
Foo
的构造函数将被修改为接受
FooManager
,而
FooManager
将:

  • 提供一个只调用
    FooInit()
    一次的幂等元
    init()
    方法
  • 销毁期间调用
    foodistroy()
当调用
foodistroy()
时,依赖项变得明确,并且
FooManager
的生存期控制。为了保持示例的简单性,我选择不讨论线程安全性,但下面是一个基本示例,其中通过使用范围管理
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);
  }
}
如果修改
Foo
的构造和控制
FooManager
的生命周期及其传递方式会产生太多的风险,那么一种折衷方法可能是通过全局变量隐藏依赖关系。但是,为了划分责任并避免执行状态,可以通过另一种类型(如智能指针)管理全局可用的
FooManager
)的生命周期。在以下代码中:

  • FooManager
    负责在构建时调用
    FooInit()
    ,在销毁时调用
    foodstroy()
  • FooManager
    的创建通过辅助功能进行管理
  • FooManager
    的生命周期由全局智能指针管理
#包括
#包括
#包括
//遗留功能。

void FooInit(){std::cout对我来说,问题似乎不是什么时候调用
FooInit
,这很简单。问题是什么时候调用
foodistroy
。除了在程序关闭时,是否还有一个可以调用它的安全点,在那里你知道不会再创建任何
Foo
实例?@Joach我不知道,每个进程都是不同的,我知道没有安全点不会创建更多的Foo实例。替换单例的经典解决方案是依赖项注入。您能否修改Foo构造函数以接受
FooInitContext
(FooInit返回的内容,无论是什么)作为一个输入参数?@utnapistim我可以,但是什么调用FooInit,什么保证它只被调用一次。此外,foodstroy()在哪里被调用?我不明白为什么
Foo
析构函数本身不能调用
foodstroy()
。或者实际上是
foodstroy
销毁了对象?(我的意思是,考虑到名字,这是有道理的)