Может ли std::map содержать ссылку на конструктор?

Есть ли способ указать на конструктор из std::map? Я хотел бы сделать следующее с кодом, который я хочу использовать в #if 0, но я не могу заставить это работать:

#include <map>
#include <functional>

using namespace std;

class Base { };
class A : public Base { };
class B : public Base { };

enum class Type { A, B, };

#if 0
using type_map_t = std::map<Type, std::function<Base*()>>;
type_map_t type_map = {
    {Type::A, &A::A},
    {Type::B, &B::B},
};
#endif

Base*
getBase(Type t)
{
#if 0
    auto constructor = type_map[t];
    return constructor();
#else
    switch(t)
    {
        case Type::A:
            return new A();
        case Type::B:
            return new B();
    }
#endif
}

int
main(int argc, char *argv[])
{
    Base *base = getBase(Type::A);
    return 0;
}

Вместо оператора switch в getBase я бы предпочел, чтобы карта указывала, какой конструктор вызывается для каждого типа.

std::function приходит на ум, как это сделать, но не представляется возможным получить адрес конструктора в C++. Есть ли элегантный способ выполнить то, что я хочу сделать здесь?


person firebush    schedule 09.09.2015    source источник
comment
Это называется заводской шаблон   -  person Slava    schedule 09.09.2015
comment
Описание заводского шаблона, который работает для вашего варианта использования: stackoverflow.com/a/954565/956880   -  person Anonymous Coward    schedule 09.09.2015


Ответы (3)


Несколько моментов:

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

  2. Из-за этого у вас нет механизма доступа к конструктору, как к обычной функции: указатель конструктора this уже известен, когда начинается построение, и нет никакого способа передать его. Способ обойти эти ограничения — использовать operator new. Обычный operator new выделит память, необходимую для объекта, и применит код конструктора, если выделение прошло успешно. (В качестве альтернативы есть «размещение new», которое позволяет программисту предоставить указатель на «достаточно памяти» для объекта. Он используется для «вставленной» конструкции, когда вы заранее выделили подходящий буфер. Обычно этот материал находится в только контейнерные библиотеки.)

Итак, то, что вы поместите в свою карту, должно будет использовать new в функциях. Обычный new подойдет, поскольку у вас нет механизма для передачи необработанной памяти.

Используя лямбда-выражения, ваша карта может выглядеть так:

type_map_t type_map = {
    {Type::A, []() -> Base* { return new A; } },
    {Type::B, []() -> Base* { return new B; } },
};

Конструкция []() -> Base* говорит, что следующий блок кода обрабатывается как тело функции, не принимающее аргументов (ничего в списке ()) и ничего из окружающей области видимости (ничего в списке []) и возвращающее Base*. Его можно использовать в качестве значения инициализации для объекта держателя std::function<Base*()>.

Вы также можете напрямую вызвать запись карты:

Base* base = type_map[Type::A]();
person TonyB    schedule 10.09.2015

Вы не можете взять адрес конструктора в соответствии со стандартом C++ (§ 12.1.12 C++98/03 и § 12.1.10 C++11):

Конструкторы - адрес конструктора не берется.

Для этой проблемы типичным решением является создание конкретных фабрик/методов, которые создают объект.

person CyberGuy    schedule 09.09.2015

Хотя напрямую получить указатель на ctor невозможно, вы можете создавать свои собственные объекты ctor:

template<class Sig>
struct ctor_t;

template<class T, class...Args>
struct ctor_t<T(Args...)> {
  T* operator()(Args...args)const {
    return new T(std::forward<Args>(args)...);
  }
  T* operator()(void*p, Args...args)const {
    return new(p) T(std::forward<Args>(args)...);
  }
  using simple = T*(*)(Args...args);
  using placement = T*(*)(void*, Args...args);
  operator simple()const {
    return [](Args...args)->T* {
      return ctor_t{}(std::forward<Args>(args)...);
    };
  }
  operator placement()const {
    return [](void*p, Args...args)->T* {
      return ctor_t{}(p, std::forward<Args>(args)...);
    };
  }
};

template<class Sig>
static const ctor_t<Sig> ctor = {};

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

живой пример.

Вышеприведенное использует некоторый С++ 14. Замените ctor на:

template<class Sig>
constexpr ctor_t<Sig> ctor() { return {}; }

и его использование от ctor<A()> до ctor<A()>() для C++11 (без шаблонов переменных).

person Yakk - Adam Nevraumont    schedule 09.09.2015
comment
Я ценю этот поучительный ответ, но я приму лямбда-ответ TonyB за его относительную простоту. - person firebush; 16.09.2015