在文件范围内使用具有副作用的 C++ 函数,访问单例

C++ Function with side-effect used at file scope, accesses singleton

我用下面的静态方法写了一个class:

MyMap& Manager::GetMap( void )
{
    static MyMap* factories = new MyMap();

    return ( *factories );
}

其中 "MyMap" 是一个类型定义:

unordered_map<string, function<Base* ( Dependency& d )>>

还有多种从 Base 派生的类型,例如

class Derived1 : public Base
{
public:
    Derived1( Dependency& d );
};

考虑以下用法。

我在 Derived1 的实现文件中定义了以下内容:

#include "Derived1.h"
#include "Manager.h"

int RegisterDerived1( void )
{
    Manager::GetMap()["Test"] = []( Dependency& d ){ return new Derived1( d ); };

    return 0;
}

int Reg = RegisterDerived1();

您不能在文件范围内调用函数,但您可以将函数的 return 值分配给全局变量,即使该函数有副作用。因此,在使用 "Manager" 时,"MyMap" 将包含 string/function 对,用于 "Base" 的各种派生类型(到目前为止)。目的是 "Base" 的新派生类型向 "Manager" 注册自己,能够构造该类型的实例和 select 哪个类型基于名称。

我想知道这是否代表安全行为and/or是否有替代实现来获得预期效果?

我已经知道这篇文章提出了一个通用注册对象,该对象在其构造函数中采用上述对并进行注册,然后为每个 class 定义一个静态实例已注册。

http://accu.org/index.php/journals/597

原理没问题

您可能需要考虑的一些事项:

  1. 返回原始指针是个坏主意 - 请改用 unique_ptr。

  2. 您真的希望 Dependency& 引用是非常量吗?

  3. 隐藏内部实现。用户无需知道(或关心)它是 unordered_map.

带有内联注释的略微修改版本供您考虑:

#include <functional>
#include <unordered_map>
#include <memory>
#include <string>

struct Base
{
  virtual ~Base() = default;
};

struct Dependency
{

};

struct Manager
{
  // I notice that Depdendency& is not const. Was that what you wanted?
  using factory_function = std::function<std::unique_ptr<Base> ( Dependency& d )>;

  // public registration function hides internal implementation of map
  static bool register_function(const std::string ident, factory_function f)
  {
    return GetMap().emplace(std::move(ident), std::move(f)).second;
  }

  // public create function hides internal implementation of map
  // returns a unique_ptr - much better!
  static std::unique_ptr<Base> create(const std::string& ident, Dependency& d)
  {
    // this will throw an exception if the factory does not exist.
    // another implementation could substitute a known version of Base,
    // for example. But now it's under your control and the user does
    // not have to think about it.
    return GetMap().at(ident)(d);
  }

  private:

  using MyMap = std::unordered_map<std::string, factory_function>;

  // private map implementation. In future we may want to add a mutex
  // (in case the map can be dynamically updated?)
  // so let's encapsulate
  static MyMap& GetMap()
  {
    // no need for new here. Static variables are cleanly destructed at
    // the end of the program, and initialised the first time the code
    // flows over them.
    static MyMap _map;
    return _map;
  }
};

struct Derived1 : Base
{
  Derived1(Dependency&) {}
};

// now we don't need to care about Manager's implementation.
// this is better - we are decoupled.
bool derived1_registered = Manager::register_function("Derived1", 
                                                    [](Dependency& d)
                                                    {
                                                      return std::make_unique<Derived1>(d);
                                                    });

int main()
{
  Dependency d;
  auto p = Manager::create("Derived1", d);

  return 0;
}