smart pointer c++ smart-pointers virtual-functions return-type

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 oculta Base::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.