Covarianza de Tipo de Retorno con Punteros Inteligentes


En C++ podemos hacer esto:

struct Base
{
   virtual Base* Clone() const { ... }
   virtual ~Base(){}
};

struct Derived : Base
{
   virtual Derived* Clone() const {...} //overrides Base::Clone
};

Sin embargo, lo siguiente no hará el mismo truco:

struct Base
{
   virtual shared_ptr<Base> Clone() const { ... }
   virtual ~Base(){}
};

struct Derived : Base
{
   virtual shared_ptr<Derived> Clone() const {...} //hides Base::Clone
};

En este ejemplo Derived::Clone cueros Base::Clone en lugar de lo reemplaza, porque el estándar dice que el tipo de retorno de un miembro sobreescrito solo puede cambiar de referencia(o puntero) a base a referencia (o puntero) a derivado. ¿Hay alguna solución inteligente para esto? Por supuesto, se podría argumentar que la función Clone debería devolver un puntero plano de todos modos, pero vamos a olvidarlo por ahora - esto es solo un ejemplo ilustrativo. Estoy buscando una manera de habilitar el cambio del tipo de retorno de una función virtual de un puntero inteligente a Base a un puntero inteligente a Derived.

Gracias de antemano!

Actualizar: Mi segundo ejemplo de hecho no compila, gracias a Iammilind

Author: Armen Tsirunyan, 2011-08-03

4 answers

No puedes hacerlo directamente, pero hay un par de formas de simularlo, con la ayuda del lenguaje de Interfaz no Virtual.

Use covarianza en punteros crudos, y luego envuélvalos

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()); }
};

Esto solo funciona si realmente tiene un puntero raw para comenzar.

Simular la covarianza lanzando

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()); }
};

Aquí debe asegurarse de que todas las sobreescrituras de Derived::doClone realmente devuelven punteros a Derived o una clase derivada de ella.

 41
Author: ymett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-03 10:42:05

En este ejemplo Derived::Clone oculta Base::Clone en lugar de lo reemplaza

No, no lo oculta. En realidad, es un error de compilación .

No se puede sobrescribir ni ocultar una función virtual con otra función que solo difiere en el tipo de retorno; por lo que el tipo de retorno debe ser el mismo o covariante, de lo contrario el programa es ilegal y por lo tanto el error.

Así que esto implica que no hay otra manera por la cual podemos convertir shared_ptr<D> en shared_ptr<B>. La única manera es tener B* y D* relación (que ya descartó en la pregunta).

 2
Author: iammilind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-21 04:00:28

Hay una mejora en una gran respuesta por @ymett usando CRTP técnica. De esa manera no necesita preocuparse por olvidarse de agregar una función no virtual en la Derivada.

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
Author: Wormer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-09 12:47:23

Algunas ideas vienen a mi mente. Primero, si puedes hacer la primera versión, simplemente deja que Clone se oculte, y escriba otro _clone protegido que realmente devuelva el puntero derivado. Ambos Clone pueden hacer uso de ella.

Eso nos lleva a la pregunta de por qué quieres hacerlo de esta manera. Otra forma podría ser una función coerce (outside), en la que recibes un shared_ptr<Base> y puedes coaccionar a un shared_ptr<Derived> si es posible. Tal vez algo parecido a:

template <typename B, typename D>
shared_ptr<D> coerce(shared_ptr<B>& sb) throw (cannot_coerce)
{
// ...
}
 0
Author: Diego Sevilla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-03 10:18:43