Я думаю, что эта задача очень распространена, но я не могу найти правильного решения.
У меня есть иерархия «продуктов», которые имеют некоторые «черты», поэтому я решил использовать шаблонный интерфейс для продуктов, где параметром шаблона является «черта»:
Это черты:
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>
…
Я не хороший заводской разработчик, может быть, я не понимаю базовых принципов и концепций. Может ли кто-нибудь помочь исправить этот код или этот подход. Спасибо!
При воссоздании этого я получаю сообщение об ошибке (немного отличающееся от того, что вы опубликовали):
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
введены в основную функцию. Это проблема, которую вы должны решить, изменив базовый интерфейс соответствующим образом.
Других решений пока нет …