Подтвердить что ты не робот

Ковариация типа возврата с интеллектуальными указателями

В С++ мы можем это сделать:

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

4b9b3361

Ответ 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 { ... }    
};

И повторное выполнение того же (с тем же возвращаемым типом) в производном классе.