2008-11-03 25 views
23

Como escribió a Scott Myers, usted puede tomar ventaja de una relajación en el tipo de sistema C++ 's para declarar clone() para devolver un puntero al tipo real de ser declarado:¿Cuál es la mejor firma para clonar() en C++?

class Base 
{ 
    virtual Base* clone() const = 0; 
}; 

class Derived : public Base 
{ 
    virtual Derived* clone() const 
}; 

El compilador detecta que el clon() devuelve un puntero al tipo del objeto, y permite que Derived lo anule para devolver un puntero a derivado.

Sería deseable tener clone() devuelve un puntero inteligente que implica la transferencia de la semántica de propiedad, como la siguiente:

class Base 
{ 
    virtual std::auto_ptr<Base> clone() const = 0; 
}; 

class Derived : public Base 
{ 
    virtual std::auto_ptr<Derived> clone() const; 
}; 

Por desgracia, la relajación de las convenciones no se aplica a los punteros inteligentes con plantilla, y el compilador no permitirá la anulación.

Por lo tanto, parece que me quedo con dos opciones:

  1. que el Clon() devuelve un puntero "tonto", y el documento que los clientes son responsables de la eliminación de la misma.
  2. Haga que clone() devuelva un puntero de base inteligente, y haga que los clientes usen dynamic_cast para guardarlos en un puntero Derivado si lo necesitan.

¿Se prefiere uno de estos enfoques? ¿O hay una manera para que yo pueda comer mi semántica de transferencia de propiedad y tener mi fuerte seguridad también?

Respuesta

5

Depende de su caso de uso. Si alguna vez piensa que necesitará llamar al clone en un objeto derivado cuyo tipo dinámico conoce (recuerde, el punto completo de clone es permitir la copia sin conociendo el tipo dinámico), entonces probablemente deba devolver un puntero tonto y cargar eso en un puntero inteligente en el código de llamada. De lo contrario, solo debe devolver un smart_ptr y, por lo tanto, puede devolverlo en todas las anulaciones.

+0

En realidad, esto provocó lo que quería decir - que podría llamar directamente nuevo con el constructor de copia cuando necesitaba esa clase en particular. – JohnMcG

0

Puede tener dos métodos, un clon virtual() que devuelve un contenedor de puntero inteligente alrededor del tipo base y un clone2 no virtual() que devuelve el tipo correcto de puntero inteligente.

clone2 obviamente se implementaría en términos de clonar y encapsular el molde.

De esta forma puede obtener el puntero inteligente más derivado que conoce en tiempo de compilación. Puede que no sea el tipo más derivado en general, pero utiliza toda la información disponible para el compilador.

Otra opción sería crear una versión de plantilla de clon que acepte el tipo que está esperando, pero que agrega más carga a la persona que llama.

7

Creo que la semántica de la función es tan clara en este caso que hay poco espacio para la confusión. Así que creo que puede usar la versión covariante (la que devuelve un puntero tonto al tipo real) con una conciencia tranquila, y las personas que llaman sabrán que están recibiendo un nuevo objeto cuya propiedad se les transfiere.

+0

+1, permite al usuario elegir qué estrategia de administración de memoria es mejor para él ... además, también se recomienda su uso con Boost Pointer Container :) –

19

La sintaxis no es tan buena, pero si agrega esto a su código anterior, ¿no soluciona todos sus problemas?

template <typename T> 
std::auto_ptr<T> clone(T const* t) 
{ 
    return t->clone(); 
} 
+4

Me gusta. Para que parezca que las cosas del estándar proponen un cambio de nombre a make_clone() (Como make_pair <>). –

+0

Consejo de bonificación: conviértalo en un amigo, y haz que el miembro clone() sea privado. – MSalters

+0

Nitpick: Me gustaría que la función acepte 'T const & t' en su lugar - aquí no hay necesidad de usar un puntero. Aparte de eso, realmente me gusta este enfoque. – cdhowie

1

Esa es una razón para usar en lugar de boost::intrusive_ptrshared_ptr o auto/unique_ptr.El puntero sin formato contiene el recuento de referencias y se puede usar de forma más uniforme en situaciones como esta.

2

Tr1::shared_ptr<> se puede convertir como si fuera un puntero sin formato.

Creo que tener clone() devolver un puntero shared_ptr<Base> es una solución bastante limpia. Puede convertir el puntero a shared_ptr<Derived> por medio de tr1::static_pointer_cast<Derived> o tr1::dynamic_pointer_cast<Derived> en caso de que no sea posible determinar el tipo de objeto clonado en tiempo de compilación.

Para garantizar el tipo de objeto es predecible que puede utilizar un molde polimórficos para shared_ptr como éste:

template <typename R, typename T> 
inline std::tr1::shared_ptr<R> polymorphic_pointer_downcast(T &p) 
{ 
    assert(std::tr1::dynamic_pointer_cast<R>(p)); 
    return std::tr1::static_pointer_cast<R>(p); 
} 

La sobrecarga añadida por la aserción será desechada en la versión de lanzamiento.

+0

¿Qué es el !! ¿para? – mmocny

+0

Pensé que era obligatorio forzar la conversión implícita a bool: en cambio, es redundante. Gracias –

26

Uso Público el patrón no virtual/privada virtual:

class Base { 
    public: 
    std::auto_ptr<Base> clone() { return doClone(); } 
    private: 
    virtual Base* doClone() { return new (*this); } 
}; 
class Derived : public Base { 
    public: 
    std::auto_ptr<Derived> clone() { return doClone(); } 
    private: 
    virtual Derived* doClone() { return new (*this); } 
}; 
+8

O 'std :: unique_ptr <>' desde C++ 11. – MSalters

1

Actualización MSalters answer para C++ 14:

#include <memory> 

class Base 
{ 
public: 
    std::unique_ptr<Base> clone() const 
    { 
     return do_clone(); 
    } 
private: 
    virtual std::unique_ptr<Base> do_clone() const 
    { 
     return std::make_unique<Base>(*this); 
    } 
}; 

class Derived : public Base 
{ 
private: 
    virtual std::unique_ptr<Base> do_clone() const override 
    { 
     return std::make_unique<Derived>(*this); 
    } 
} 
+0

¿Un clon no virtual? !!! – curiousguy

+0

@curiousguy ¡Vaya! Muy agradecido... – Daniel

Cuestiones relacionadas