Ковариация типа возврата с интеллектуальными указателями
В С++ мы можем это сделать:
struct Base
{
virtual Base* Clone() const { ... }
virtual ~Base(){}
};
struct Derived : Base
{
virtual Derived* Clone() const {...} //overrides Base::Clone
};
Однако следующее не будет делать тот же трюк:
struct Base
{
virtual shared_ptr<Base> Clone() const { ... }
virtual ~Base(){}
};
struct Derived : Base
{
virtual shared_ptr<Derived> Clone() const {...} //hides Base::Clone
};
В этом примере Derived::Clone
скрывает Base::Clone
вместо того, чтобы переопределять его, потому что в стандарте указано, что возвращаемый тип переопределяющего элемента может изменяться только от ссылки (или указателя) до базы к ссылке (или указатель) к полученному. Есть ли какое-нибудь умное обходное решение для этого? Конечно, можно утверждать, что функция Clone
должна возвращать простой указатель в любом случае, но пусть забудет его сейчас - это всего лишь иллюстративный пример. Я ищу способ включить изменение типа возврата виртуальной функции с интеллектуального указателя на Base
на интеллектуальный указатель на Derived
.
Спасибо заранее!
Обновление: Мой второй пример действительно не компилируется, благодаря Iammilind
Ответы
Ответ 1
Вы не можете сделать это напрямую, но есть несколько способов имитировать его с помощью идиомы не виртуального интерфейса.
Используйте ковариацию на raw указателях, а затем оберните их
struct Base
{
private:
virtual Base* doClone() const { ... }
public:
shared_ptr<Base> Clone() const { return shared_ptr<Base>(doClone()); }
virtual ~Base(){}
};
struct Derived : Base
{
private:
virtual Derived* doClone() const { ... }
public:
shared_ptr<Derived> Clone() const { return shared_ptr<Derived>(doClone()); }
};
Это работает, только если у вас есть исходный указатель для начала.
Имитировать ковариацию путем литья
struct Base
{
private:
virtual shared_ptr<Base> doClone() const { ... }
public:
shared_ptr<Base> Clone() const { return doClone(); }
virtual ~Base(){}
};
struct Derived : Base
{
private:
virtual shared_ptr<Base> doClone() const { ... }
public:
shared_ptr<Derived> Clone() const
{ return static_pointer_cast<Derived>(doClone()); }
};
Здесь вы должны убедиться, что все переопределения Derived::doClone
действительно возвращают указатели на Derived
или класс, полученный из него.
Ответ 2
В этом примере Derived::Clone
скрывает Base::Clone
вместо того, чтобы переопределять его
Нет, он не скрывает его. На самом деле, это ошибка компиляции.
Вы не можете переопределить или скрыть виртуальную функцию с помощью другой функции, которая отличается только от типа возврата; поэтому тип возврата должен быть таким же или covariant, в противном случае программа является незаконной и, следовательно, является ошибкой.
Итак, это означает, что нет другого способа, с помощью которого мы можем преобразовать shared_ptr<D>
в shared_ptr<B>
. Единственный способ - иметь отношения B*
и D*
(которые вы уже исключили в вопросе).
Ответ 3
Некоторые идеи приходят мне в голову. Во-первых, если вы можете сделать первую версию, просто оставьте это Clone
, чтобы скрыть, и напишите еще один защищенный _clone
, который на самом деле возвращает производный указатель. Оба Clone
могут использовать его.
Это приводит к вопросу о том, почему вы так хотите. Другим способом может быть функция принуждения (снаружи), в которой вы получаете shared_ptr<Base>
, и, если возможно, можете принудить ее к shared_ptr<Derived>
. Может быть, что-то вроде:
template <typename B, typename D>
shared_ptr<D> coerce(shared_ptr<B>& sb) throw (cannot_coerce)
{
// ...
}
Ответ 4
Вы не можете. Возможный подход:
struct Base
{
virtual shared_ptr<Base*> Clone() const { ... }
};
И повторное выполнение того же (с тем же возвращаемым типом) в производном классе.