Метод в базовом классе, который возвращает тип производного класса?
У меня есть куча классов, которые имеют одну общую функцию, за исключением того, что она возвращает указатель на их собственный тип. Код выглядит так же, и я хотел бы переместить его в абстрактный базовый класс. Но как я могу заставить классы, которые наследуют от него, возвращать свой собственный тип?
class base {
base *foo() {
// ...
}
};
class derived : public base {
};
derived d;
d.foo(); // Should return derived* instead of base*
Есть ли способ выразить это в C++?
2 ответа
Да, C++ поддерживает это. Это называется ковариантным типом возврата. Вам просто нужно объявить функцию виртуальной и соответственно объявить возвращаемые типы. Это все, что нужно сделать.
struct base {
virtual base *foo() {
// ...
}
};
struct derived : public base {
virtual derived *foo() {
// ...
}
};
derived d;
base *base_ptr = d.foo();
Теперь ваш комментарий расширяет исходный вопрос:
Но на самом деле моя цель - не повторять тело функции, поскольку оно такое же, как и используемый тип.
Это невозможно.
Существуют различные техники, которые могут облегчить повторение, но вы не сможете обойти тот факт, что что бы вы ни делали, вам все равно придется создавать функциональные тела самостоятельно.
Одним из таких методов будет использование макросов за счет запутывания и всех других недостатков, связанных с макросами; но все же макрос не появится в классах автоматически. Вы должны положить его туда.
// beware of macros!
#define FOO(T) virtual T *foo() { return new T; }
struct base {
FOO(base)
virtual ~base() {} // let's not forget the virtual destructor
};
struct derived : public base {
FOO(derived)
};
Аналогичный подход заключается в облегчении повторения тел функций с шаблоном:
template <class T>
T *ComplicatedFunctionReturningT()
{
T *t;
// ...
// ...
// ...
return t;
}
struct base {
virtual base *foo() {
return ComplicatedFunctionReturningT<base>();
}
virtual ~base() {} // let's not forget the virtual destructor
};
struct derived : public base {
virtual derived *foo() {
return ComplicatedFunctionReturningT<derived>();
}
};
Шаблоны безопаснее макросов.
Другой подход заключается в использовании шаблона проектирования Template Template. Если большая часть кода повторяется в теле функции каждого класса, попытайтесь переместиться как можно больше в базовый класс и поместите небольшую абстрактную часть в закрытую функцию, которая будет переопределена:
class base {
public:
base *foo() { // no longer virtual
// ...
// ...
base *ptr = fooImpl();
// ...
// ...
return ptr;
}
virtual ~base() {} // let's not forget the virtual destructor
private:
virtual base *fooImpl() = 0; // pure virtual and private
};
class derived1 : public base {
private:
virtual derived1 *fooImpl() {
return new derived1; // very simple body
}
};
class derived2 : public base {
private:
virtual derived2 *fooImpl() {
return new derived2; // very simple body
}
};
Конечно, все это действительно того стоит, только если функциональные тела действительно сложны. В крайних случаях совершенно другой подход заключается в создании кода C++ с использованием какого-либо внешнего инструмента или сценария.
И, наконец, если это действительно проблема, пересмотрите свой дизайн в целом. Возможно, вам не нужна эта функция, или вам не нужен ООП для реальной проблемы, которую пытается решить ваша программа.
Исходя из вашего комментария к ответу христиан, вы можете реализовать вспомогательный метод шаблона, чтобы не дублировать код, который вы будете использовать:
class base
{
protected:
template<class T> T* fooInternal()
{
T* t = new T();
// do stuff with t
return t;
}
public:
virtual base* foo() { return fooInternal<base>(); }
};
class derived : public base
{
public:
virtual derived* foo() { return fooInternal<derived>(); }
};
Один из вариантов - использовать CRTP (любопытно повторяющийся шаблон шаблона).
template<typename Derived>
struct base {
Derived* foo() {
// ...
return static_cast<Derived*>(this);
}
};
struct derived : base<derived> {
// ...
};
derived d;
derived *derived_ptr = d.foo();
Основное преимущество состоит в том, что вам не нужны ни макросы, ни виртуальные методы для получения нужного вам типа.
Если вы делаете это с несколькими методами в базовом классе, было бы неплохо определить вспомогательный метод.
template<typename Derived>
struct base {
Derived* foo() {
// ...
return self();
}
private:
Derived* self() {
return static_cast<Derived*>(this);
}
};
Я использовал этот шаблон, например, для объектов свойств, где у меня есть базовый набор общих свойств и несколько производных объектов свойств со своими собственными свойствами. Затем у меня есть методы базового класса, возвращающие ссылку на производный класс, чтобы я мог связать установку нескольких свойств в одном операторе.
SomePropertyObject()
.setBaseProperty("foo")
.setDerivedProperty("bar");