smart pointer c++
Tipo de devoluciĆ³n Covarianza con punteros inteligentes (4)
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
oculta Base::Clone
lugar de anularlo , porque el estándar dice que el tipo de retorno de un miembro sobrescrito puede cambiar solo 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 olvidémoslo 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 la Base
a un puntero inteligente a Derived
.
¡Gracias por adelantado!
Actualización: Mi segundo ejemplo no se compila, gracias a Iammilind
En este ejemplo,
Derived::Clone
ocultaBase::Clone
lugar de anularlo
No , no lo oculta. En realidad, es un error de compilación .
No puede anular ni ocultar una función virtual con otra función que difiera solo en el tipo de retorno; por lo tanto, el tipo de retorno debe ser el mismo o covariant , de lo contrario el programa es ilegal y, por lo tanto, el error.
Entonces, esto implica que no hay otra manera por la cual podamos convertir shared_ptr<D>
en shared_ptr<B>
. La única forma es tener una relación B*
y D*
(que ya descartó en la pregunta).
Algunas ideas vienen a mi mente. Primero, si puede hacer la primera versión, simplemente deje ese Clone
para ocultar y escriba otro _clone
protegido que devuelva el puntero derivado. Ambos Clone
pueden hacer uso de él.
Eso nos lleva a la pregunta de por qué quieres que sea así. Otra forma podría ser una función de coerción (externa), en la que recibe un shared_ptr<Base>
y puede coaccionarlo con un shared_ptr<Derived>
si es posible. Tal vez algo en la línea de:
template <typename B, typename D>
shared_ptr<D> coerce(shared_ptr<B>& sb) throw (cannot_coerce)
{
// ...
}
Hay una mejora en una gran answer por @ymett usando CRTP técnica CRTP . De esa manera, no tiene que preocuparse por olvidar agregar una función no virtual en Derivado.
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 { ... }
};
No puede hacerlo directamente, pero hay un par de formas de simularlo, con la ayuda del lenguaje de interfaz no virtual.
Use la covarianza en los punteros en bruto, 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 en bruto para comenzar.
Simular la covarianza por casting
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 anulaciones de Derived::doClone
devuelvan los punteros a Derived
o una clase derivada de él.