Функция C++ с побочным эффектом, используемая в области файла, обращается к синглтону

Я написал класс со следующим статическим методом:

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

    return ( *factories );
}

Где "MyMap" - это typedef для:

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();

Вы не можете вызывать функции в области файла, но вы можете присвоить возвращаемое значение функции глобальной переменной, даже если эта функция имеет побочные эффекты. Следовательно, к тому времени, когда «Менеджер» будет использоваться, «Моя карта» будет содержать пары строк/функций для различных производных типов «Базы» (пока что). Цель состоит в том, чтобы новые производные типы «Base» регистрировались в «Manager», могли создавать экземпляры этого типа и выбирать тип на основе имени.

Мне интересно, представляет ли это безопасное поведение и/или есть ли альтернативные реализации для получения желаемого эффекта?

Я был проинформирован об этой статье, в которой предлагается общий объект регистрации, который принимает указанную выше пару в своем конструкторе и выполняет регистрацию, статический экземпляр которого затем определяется для каждого регистрируемого класса.

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


person Scott Oliver    schedule 13.04.2016    source источник
comment
Кажется, что вы в основном спрашиваете Lazy Initialization, а другие вещи, связанные с кодом, не очень важны. Считаете ли вы, что этот пост отвечает на ваш вопрос: ленивая инициализация с одноэлементным шаблоном. Если да, то его можно закрыть как дубликат.   -  person iammilind    schedule 13.04.2016
comment
@iammilind Мне ясно, как работает реализация синглтона, и я думаю, что это то, о чем этот вопрос. Я просто включил другие биты для контекста. Что мне менее ясно, так это последствия использования вышеуказанной бесплатной функции в области файла. Это похоже на Плохую вещь, так как мне нужно обойти ограничение.   -  person Scott Oliver    schedule 13.04.2016


Ответы (1)


Принцип в порядке.

Несколько вещей, которые вы можете рассмотреть:

  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;
}
person Richard Hodges    schedule 13.04.2016
comment
Да, в этом классе зависимостей меня интересуют неконстантные методы. А вот пункты 1 и 3 хорошо проработаны. Любые мысли об использовании бесплатной функции таким образом? - person Scott Oliver; 13.04.2016
comment
бесплатная функция абсолютно прекрасна. Вы даже можете создать шаблон, если хотите, поэтому вам нужно написать его только один раз. - person Richard Hodges; 13.04.2016