2009-03-12 11 views
16

Creo que se me ha preguntado antes, pero no puedo encontrarlo en SO, ni puedo encontrar nada útil en Google. Tal vez "covariante" no es la palabra que estoy buscando, pero este concepto es muy similar a los tipos de retorno covariante en las funciones, por lo que creo que es probablemente correcto. Esto es lo que quiero hacer y me da un error del compilador:Plantillas de covariantes de C++

class Base; 
class Derived : public Base; 

SmartPtr<Derived> d = new Derived; 
SmartPtr<Base> b = d; // compiler error 

Asumir esas clases se plasmen plenamente ... Creo que se entiende la idea. No puede convertir un SmartPtr<Derived> en un SmartPtr<Base> por algún motivo poco claro. Recuerdo que esto es normal en C++ y en muchos otros idiomas, aunque por el momento no puedo recordar por qué.

Mi pregunta de raíz es: ¿cuál es la mejor manera de realizar esta operación de asignación? Actualmente, estoy sacando el puntero del SmartPtr, actualizándolo explícitamente al tipo base, y luego envolviéndolo en un nuevo SmartPtr del tipo apropiado (tenga en cuenta que esto no está filtrando recursos porque nuestra clase de SmartPtr de fabricación propia utiliza una referencia intrusiva contando). Eso es largo y desordenado, especialmente cuando necesito envolver el SmartPtr en otro objeto ... ¿cualquier atajo?

Respuesta

11

Tanto el constructor de copia como el operador de asignación deberían poder tomar un SmartPtr de un tipo diferente e intentar copiar el puntero de uno a otro. Si los tipos no son compatibles, el compilador se quejará, y si son compatibles, habrá resuelto su problema. Algo como esto:

template<class Type> class SmartPtr 
{ 
    .... 
    template<class OtherType> SmartPtr(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> copy constructor 

    template<class OtherType> SmartPtr<Type> &operator=(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> assignment operator 
}; 
+0

ocultar comentarios @MSN: He aprendido por prueba y error que esto no es suficiente para cumplir con el constructor de copias y el operador de asignaciones "normales". Por lo tanto, debe implementar ambos: SmartPtr (const SmartPtr &) AND template SmartPtr (const SmartPtr &) (igual para op =) – mmmmmmmm

+0

Bueno, sí, eso es lo que quise decir :) – MSN

+0

De C++ 11 en adelante también agregas move-constructor y move-assignment (ésos con '&&'). –

3

Depende de la clase SmartPtr. Si tiene un constructor de copia (o en su caso, operador de asignación) que toma SmartPtr<T>, donde T es el tipo con el que se construyó, entonces no va a funcionar, porque SmartPtr<T1> no está relacionado con SmartPtr<T2> incluso si T1 y T2 son relacionado por herencia.

Sin embargo, si SmartPtr tiene un operador de constructor de copia/asignación a plantillas, con parámetro de plantilla TOther, que acepta SmartPtr<TOther>, entonces debería funcionar.

+0

esto parece brillante ... Necesito probar esto lo antes posible para ver si funciona. Lamentablemente, estoy usando MSVC6, que tiene problemas importantes con las plantillas y IIRC descartará las funciones de plantillas dentro de las clases con plantillas, incluso si especifica explícitamente los parámetros de la plantilla al llamarlo. – rmeador

+0

¿De verdad? - Sé que he hecho esto exactamente bajo MSVC6. – Eclipse

+0

Escribí una clase SmartPtr que hace esto bien bajo MSVC6, por lo que debería estar bien. –

2

Asumiendo que tiene el control de la clase SmartPtr, la solución es proporcionar un constructor de plantilla:

template <class T> 
class SmartPtr 
{ 
    T *ptr; 
public: 

    // Note that this IS NOT a copy constructor, just another constructor that takes 
    // a similar looking class. 
    template <class O> 
    SmartPtr(const SmartPtr<O> &src) 
    { 
     ptr = src.GetPtr(); 
    } 
    // And likewise with assignment operator. 
}; 

Si los tipos T y S son compatibles, funcionará, si ellos no son obtendrás un error de compilación.

+0

No siempre es así de simple; debe asegurarse de que se mantenga el recuento de referencias adecuado. No puede eliminar el objeto Derivado después de que el último Ptr haya desaparecido cuando aún haya Ptr en el mismo Derivado. – MSalters

+0

Bueno, sí, aún tendrá que asegurarse de implementar la inteligencia del puntero inteligente. – Eclipse

15

SmartPtr<Base> y SmartPtr<Derived> son dos ejemplos distintos de una plantilla de SmartPtr. Estas nuevas clases no comparten la herencia que Base y Derived hacen. Por lo tanto, tu problema.

what is the best way to perform this assignment operation?

SmartPtr<Base> b = d; 

no invoca operador de asignación. Esto invoca la copia-ctor (la copia se elide en la mayoría de los casos) y es exactamente como si escribió:

SmartPtr<Base> b(d); 

Proporcionar una copia-ctor que toma un SmartPtr<OtherType> y ponerlo en práctica. Lo mismo aplica para el operador de asignación. Tendrá que escribir el copy-ctor y op = teniendo en cuenta la semántica de SmartPtr.

+0

Soy consciente de que no funciona, y ahora estoy consciente (gracias) de que se debe a la falta de relación entre las dos clases de plantillas. Estoy preguntando cómo hacer que se comporte como si estuvieran relacionados, quizás añadiendo algún tipo de ctor inteligente u otro truco. – rmeador

+2

Mucho depende de la semántica exacta de la clase SmartPtr, p. ¿Tiene transferencia de propiedad, hace referencia de conteo, etc. Tendrá que escribir el copy-ctor y op = teniendo en cuenta la semántica de SmartPtr. – dirkgently

5

Las plantillas no son covariantes, y eso es bueno; Imaginemos lo que sucedería en el caso siguiente:

vector<Apple*> va; 
va.push_back(new Apple); 

// Now, if templates were covariants, a vector<Apple*> could be 
// cast to a vector<Fruit*> 
vector<Fruit*> & vf = va; 
vf.push_back(new Orange); // Bam, we just added an Orange among the Apples! 

para lograr lo que estamos tratando de hacer, la clase SmartPointer debe tener un constructor a plantillas, que se lleva a cualquiera otra SmartPointer o un puntero de otro tipo. Puedes echar un vistazo a boost :: shared_ptr, que hace exactamente eso.

template <typename T> 
class SmartPointer { 

    T * ptr; 

    public: 
    SmartPointer(T * p) : ptr(p) {} 
    SmartPointer(const SmartPointer & sp) : ptr(sp.ptr) {} 

    template <typename U> 
    SmartPointer(U * p) : ptr(p) {} 

    template <typename U> 
    SmartPointer(const SmartPointer<U> & sp) : ptr(sp.ptr) {} 

    // Do the same for operator= (even though it's not used in your example) 
}; 
+0

Haces un excelente punto con tu ejemplo ... No lo había considerado. En el caso de un puntero inteligente, sin embargo, no creo que te encuentres con ese problema porque la interfaz de clase es esencialmente la misma que la interfaz del tipo apuntado (a través de sobrecargado -> y *). – rmeador

+0

Creo que su ejemplo es un ejemplo erróneo, ya que no es realmente covariante exactamente, porque puede poner algo así como una naranja en la lista. Solo será covariante si la interfaz pública solo regresa _ Fruit *, pero no _accepts_ Fruit *. C# proporciona las palabras clave "in" y "out" para hacer que los tipos genéricos sean covariantes o contravariantes, respectivamente. Implica de forma estática cómo se usan los tipos para evitar la situación que describió. – LostSalad

0

Creo que lo más fácil es para proporcionar la conversión automática a otro SmartPtr de acuerdo a lo siguiente:

template <class T> 
class SmartPtr 
{ 
public: 
    SmartPtr(T *ptr) { t = ptr; } 
    operator T *() const { return t; } 
    template <class Q> operator SmartPtr<Q>() const 
    { return SmartPtr<Q>(static_cast<Q *>(static_cast<T *>(* this))); } 
private: 
    T *t; 
}; 

Tenga en cuenta que esta aplicación es robusto en el sentido de que la plantilla operador de conversión no necesita conocer la semántica del puntero inteligente, por lo que el recuento de referencias no necesita replicarse, etc.

+0

Prueba {return SmartPtr (t); } El compilador le dirá si se puede asignar una T * a Q * sin todos los moldes. Asegúrese de que su lógica de recuento de referencia pueda compartir el recuento de referencias entre los tipos de plantilla. Un recuento de referencias int * debería ser capaz de hacerlo. – jmucchiello