2011-08-25 20 views
12

Perdón por el título largo pero quería ser específico. me espera el siguiente código para trabajar pero no y no puedo entender por qué:/¿Cómo es que un puntero a una clase derivada no se puede pasar a una función esperando una referencia a un puntero a la clase base?

#include <cstdio> 
#include <cassert> 

class UniquePointer 
{ 
public: 
    void Dispose() 
    { 
     delete this; 
    } 

    friend void SafeDispose(UniquePointer*& p) 
    { 
     if (p != NULL) 
     { 
      p->Dispose(); 
      p = NULL; 
     } 
    } 
protected: 
    UniquePointer() { } 
    UniquePointer(const UniquePointer&) { } 
    virtual ~UniquePointer() { } 
}; 

class Building : public UniquePointer 
{ 
public: 
    Building() 
    : mType(0) 
    {} 
    void SetBuildingType(int type) { mType = type; } 
    int GetBuildingType() const { return mType; } 
protected: 
    virtual ~Building() { } 
    int mType; 
}; 

void Foo() 
{ 
    Building* b = new Building(); 
    b->SetBuildingType(5); 
    int a = b->GetBuildingType(); 
    SafeDispose(b);  // error C2664: 'SafeDispose' : cannot convert parameter 1 from 'Building *' to 'UniquePointer *&' 
    b->Dispose(); 
} 

int main(int argc, char* argv[]) 
{ 
    Foo(); 
    return 0; 
} 
+1

Tenga en cuenta que no hay nada "seguro" sobre lo que está tratando de hacer. Si 'b' se establece en' NULL', entonces 'b-> Dispose();' causa un comportamiento que es tan indefinido como si 'b' apuntara a un objeto eliminado. –

+0

@Charles: Editado, en espera de revisión. Este error no es parte de una pregunta, ¿verdad? –

+0

Además, no es necesario que 'SafeDispose' sea un amigo, ya que 'Dispose' es público. –

Respuesta

42

Imagínese fuera legal. Posteriormente, se podría escribir código como este:

class Animal : public UniquePointer 
{ 
}; 

void Transmogrify(UniquePointer*& p) 
{ 
    p = new Animal(); 
} 

void Foo() 
{ 
    Building* b = nullptr; 
    Transmogrify(b); 
    b->SetBuildingType(0); // crash 
} 

Observe que usted ha violado el sistema de tipos (pones un animal que un edificio debe ser) sin necesidad de un yeso o elevar un error de compilación.

+0

Veo tu punto. La razón por la que quería la referencia al puntero era poder establecer ese puntero a NULL, pero obviamente perdí todos los efectos secundarios que esto podría tener. –

3

No está permitido porque si fuera usted podría hacer lo siguiente:

friend void SafeDispose(UniquePointer*& p) 
{ 
    p = new UniquePointer(); 
} 


Building* building; 
SafeDispose(building) 
//building points to a UniquePointer not a Building. 

supongo que el trabajo en torno a que sería una función de plantilla.

+0

Excepto que no puede instanciar un puntero único, debe estar subclasificado. Pero veo tu punto. –

6

No creo que sea posible hacerlo funcionar de la manera que lo tiene diseñado. En su lugar, intente lo siguiente:

template <typename T> 
void SafeDispose(T * & p) 
{ 
    if (p != NULL) 
    { 
     p->Dispose(); 
     p = NULL; 
    } 
} 

class UniquePointer 
{ 
public: 
    void Dispose() 
    { 
     delete this; 
    } 

protected: 
    UniquePointer() { } 
    UniquePointer(const UniquePointer&) { } 
    virtual ~UniquePointer() { } 
}; 
+0

Me gusta su solución, de hecho, si usted hubiera dicho por qué no es posible hacerlo de la manera en que lo hice, esta hubiera sido mi respuesta aceptada. –

0

pienso que su SafeDispose probablemente debería parecerse más a:

friend void SafeDispose(UniquePointer** p) ... 

Para invocarlo usando

SafeDispose(&(UniquePointer*)b); 

entonces debería funcionar de esta manera.

Pero su próximo estado de

b->Dispose(); 

romperá causa b debe ahora ser NULL, porque se ha dispuesto y establecido en NULL por el método de SafeDispose.

+0

Pero '& b' es un valor de tipo' Building ** 'que no se puede pasar a una función tomando' UniquePointer ** 'por razones bastante idénticas que no pueden unir' Building * 'a un no -const 'UniquePointer * &'. –

+0

Gracias por señalar esto. Agregar un reparto probablemente solucionaría este problema, ¿verdad? (código de muestra actualizado). Entonces supongo que la respuesta wilx (mediante el uso de una plantilla) tiene más sentido. –

+1

No, un elenco no soluciona el problema porque crea uno nuevo. El elenco convierte el puntero pero el resultado del elenco es un valor r y no puede tomar la dirección de un valor r. –

1

Para responder el título de su pregunta, no puede enlazar una referencia no const a base a una instancia de clase derivada porque podría establecer esa referencia a un puntero a una instancia base que no sea derivada. Considere esta función:

void Renew(UniquePointer *& p) { 
    delete p; 
    p = new UniquePointer(); 
} 

si pudiera pasar un puntero a Building que sería capaz de establecer de forma incorrecta para que apunte a una instancia UniquePointer.

Como ya se ha sugerido, la solución es cambiar su referencia a un puntero simple. No solo esto resuelve su problema, sino que también es una mejor implementación de SafeDispose(); como lo escribiste esta función dio la falsa idea de que siempre establecerías en 0 todas tus instancias UniquePointer. Pero ¿qué pasaría si alguien escribió (suponiendo UniquePointer constructor era pública para simplificar):

UniquePointer *p1 = new UniquePointer(); 
UniquePointer *p2 = p1; 
SafeDispose(p1); 

Ellos esperarían que la totalidad de sus UniquePointer s para ser debidamente atendidos, cuando p2 es realmente válido.

+0

Probablemente hubiera dado a todo un nombre mejor que lo que hice. Quise SafeDispose para establecer el puntero a NULL y verificar NULL, pero no consideré el hecho de que el destinatario podría tener una copia del puntero. Pero es importante tener en cuenta que no hay intercambio en absoluto, estaba interesado en un sistema de "propiedad singe" después de leer el estilo de codificación de Google. –

Cuestiones relacionadas