Как правильно реализовать фабрику для шаблонных продуктов

Я думаю, что эта задача очень распространена, но я не могу найти правильного решения.

У меня есть иерархия «продуктов», которые имеют некоторые «черты», поэтому я решил использовать шаблонный интерфейс для продуктов, где параметром шаблона является «черта»:

Это черты:

struct Foo {
static std::string get_name() { return "Foo"; }

Foo(int a) : a_(a) {}
int operator()() const { return a_; }

private:
int a_;
};

struct Bar {
static std::string get_name() { return "Bar"; }

Bar(int a, int b) : a_(a), b_(b) {}
int operator()() const { return a_ + b_; }

private:
int a_;
int b_;
};

struct Spam {
Spam(int a, int b) : a_(a), b_(b), c_(0) {}
void operator()() { c_++; }

private:
int a_;
int b_;
int c_;
};

И это иерархия продуктов:

template <class T>
class Product {

public:
typedef T T_type;

virtual T get() = 0;

virtual ~Product() {}
};

template <class T>
class ProductA : public Product<T> {

typedef Product<T>   base_type;

public:
ProductA(int a) : a_(a) {}

virtual ~ProductA() {}

virtual T get() {
return T(a_);
}

private:
int a_;
};

template <class T, class U>
class ProductB : public Product<T> {

typedef Product<T>                                  base_type;

public:
typedef U                                           U_type;

ProductB(int a, int b) : a_(a), b_(b) {}

virtual ~ProductB();

virtual T get() {
init(); // U is being used here
return T(a_, b_);
}

protected:
void init() {}

private:
int a_;
int b_;
};

Мне нужно использовать дополнительный уровень наследования из-за разных интерфейсов ProductA а также ProductB — у них разные c-tors.

А вот и конкретные изделия:

class ProductA1 : public ProductA<Foo> {

typedef ProductA<Foo>  base_type;

public:
ProductA1(int a) : base_type(a) { std::cout << "A1 created" << std::endl; }

virtual ~ProductA1() { std::cout << "A1 deleted" << std::endl; }
};

class ProductB1 : public ProductB<Bar, Spam> {

typedef ProductB<Bar, Spam>  base_type;

public:
ProductB1(int a, int b) : base_type(a, b) { std::cout << "B1 created" << std::endl; }

virtual ~ProductB1() { std::cout << "B1 deleted" << std::endl; }
};

Теперь я хочу иметь механизм «унифицированного» создания продуктов (в этом примере два типа ProductA1 а также ProductB1) как-то со строкой, переданной в некоторый метод. Очевидно, мне нужна фабрика …

Поэтому я реализовал фабрики для разных ветвей иерархии (для создания объектов типов ProductA а также ProductB), чтобы я мог создавать объекты, передавая их типы через параметр шаблона:

template <class P>
struct ProductAFactory {

typedef typename P::T_type      T_type;
typedef ProductA<T_type>        product_type;

static
product_type* create(int a) {
return new P(a);
}
};template <class P>
struct ProductBFactory {

typedef typename P::T_type      T_type;
typedef typename P::U_type      U_type;
typedef ProductB<T_type,
U_type>        product_type;

static
product_type* create(int a, int b) {
return new P(a, b);
}
};

Имея эти фабрики, я должен иметь фабрику, которая должна создавать продукты необходимого типа и возвращать указатель на продукт Product<T> интерфейс:

template <class T>
class ProductFactory {

public:

static
Product<T>*
create(const std::string& product_name,
const int a,
const int b) {

const std::string product_a1 = "A1";
const std::string product_b1 = "B1";

if (product_name == product_a1)
return ProductAFactory<ProductA1>::create(a);
else if (product_name == product_b1)
return ProductBFactory<ProductB1>::create(a, b); // (*) <--- compiler error
else
throw std::runtime_error("Unsupported product: " + product_name);
}
};

Все эти коды предназначены для использования таким образом:

void main() {

typedef Foo T;

std::shared_ptr<Product<T>> p(ProductFactory<T>::create("A1", 1, 1));
T t = p->get();

std::cout << t.get_name() << ": " << t() << std::endl;
}

И тут я столкнулся с проблемой компиляции этого кода — ошибка return value type does not match the function type в (*), Кажется, что ProductB<Foo, Spam> не может быть автоматически преобразовано в его базовый тип Product<Foo>

Я не хороший заводской разработчик, может быть, я не понимаю базовых принципов и концепций. Может ли кто-нибудь помочь исправить этот код или этот подход. Спасибо!

0

Решение

При воссоздании этого я получаю сообщение об ошибке (немного отличающееся от того, что вы опубликовали):

error: cannot convert 'ProductBFactory<ProductB1>::product_type* {aka ProductB<Bar, Spam>*}' to 'Product<Foo>*' in return
return ProductBFactory<ProductB1>::create(a, b); // (*) <--- compiler error

Суть проблемы в том, что вы пытаетесь вернуть ProductB<Bar, Spam>* из функции, объявленной для возврата Product<Foo>*, Вы, кажется, думаете, что эти типы так или иначе связаны наследованием, но это не так. ProductB<Bar, Spam> на самом деле подкласс Product<Bar> — этот последний никак не связан или не может быть преобразован в Product<Foo> больше чем vector<int> это к vector<float> — то есть совсем нет. Шаблонные классы с различными параметрами шаблона полностью отличаются друг от друга.

Так что либо вы допустили ошибку в своем коде, и пытаетесь вернуть ProductB<Bar,Spam>* когда на самом деле вы должны пытаться вернуть ProductB<Foo,Spam>* (скажем), или вы неправильно понимаете полиморфные отношения (или их отсутствие) между иерархиями наследования шаблонов с различными параметрами шаблона.

ОБНОВИТЬ: В ответ на ваш комментарий: можно создать фабрику, которая создает объекты разных шаблонных типов, если у них есть общий предок в иерархии типов, который можно использовать в качестве возвращаемого типа. В вашем случае это требует переделки по крайней мере до некоторой степени. Один из способов сделать это — создать базовый класс для Product<T>, сказать ProductBase, это не зависит от шаблона:

template <class T>
class Product : public ProductBase
{
...
};

Тогда тип возврата вашего create функция может быть ProductBase*, Эквивалентно, вы можете просто вынуть шаблон из определения Product сам. В любом случае это потребует дополнительных изменений интерфейса, чтобы сделать его полезным.

Например, посмотрите это живое демо, где я реализую ProductBase подход. Таким образом, ваш пример может быть скомпилирован, но только благодаря неприятному static_cast введены в основную функцию. Это проблема, которую вы должны решить, изменив базовый интерфейс соответствующим образом.

0

Другие решения

Других решений пока нет …