Тип возврата Ковариация с помощью умных указателей - PullRequest
31 голосов
/ 03 августа 2011

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

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

Ответы [ 4 ]

45 голосов
/ 03 августа 2011

Вы не можете сделать это напрямую, но есть несколько способов смоделировать это с помощью идиомы Non-Virtual Interface.

Используйте ковариацию для необработанных указателей, а затем оберните их

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 голосов
/ 09 января 2018

Улучшен ответ от @ymett с использованием CRTP техники. Таким образом, вам не нужно беспокоиться о том, чтобы забыть добавить не виртуальную функцию в Derived.

struct Base
{
private:
   virtual Base* doClone() const { ... }

public:
   shared_ptr<Base> Clone() const { return shared_ptr<Base>(doClone()); }

   virtual ~Base(){}
};

template<class T>
struct CRTP_Base : Base
{
public:
   shared_ptr<T> Clone() const { return shared_ptr<T>(doClone()); }
};

struct Derived : public CRTP_Base<Derived>
{
private:
   virtual Derived* doClone() const { ... }
};
2 голосов
/ 03 августа 2011

В этом примере Derived::Clone скрывает Base::Clone, а не переопределяет его

Нет , это не скрывает. На самом деле, это ошибка компиляции .

Вы не можете переопределить или скрыть виртуальную функцию с помощью другой функции, которая отличается только типом возврата; поэтому тип возвращаемого значения должен быть одинаковым или ковариантным , в противном случае программа недопустима и, следовательно, содержит ошибку.

Таким образом, это означает, что нет другого способа, которым мы можем преобразовать shared_ptr<D> в shared_ptr<B>. Единственный способ - это иметь отношения B* и D* (которые вы уже исключили в вопросе).

0 голосов
/ 03 августа 2011

Некоторые идеи приходят мне в голову.Во-первых, если вы можете сделать первую версию, просто оставьте это 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)
{
// ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...