2009-02-02 7 views
12

He estado evaluando varias implementaciones de punteros inteligentes (wow, hay un MUCHO por ahí) y me parece que la mayoría de ellos se pueden clasificar en dos grandes clasificaciones:¿Cuál es la mejor manera de implementar punteros inteligentes en C++?

1) Esta categoría usa herencia en el objetos a los que se hace referencia para que tengan recuentos de referencia y, por lo general, up() y down() (o sus equivalentes) implementados. IE, para usar el puntero inteligente, los objetos a los que apunta deben heredar de alguna clase que proporciona la implementación de ref.

2) Esta categoría utiliza un objeto secundario que mantiene la cuenta de referencia. Por ejemplo, en lugar de apuntar el puntero inteligente directamente a un objeto, apunta a este objeto de metadatos ... ¿Quién tiene un recuento de referencias y las implementaciones hacia arriba() y hacia abajo (y quién suele proporcionar un mecanismo para que el puntero Acceda al objeto real al que apunta, de modo que el puntero inteligente pueda implementar correctamente el operador ->()).

Ahora, 1 tiene la desventaja de que fuerza a todos los objetos que desea referencia contar heredar de un ancestro común, y esto significa que no puede usar esto para hacer referencia a objetos de conteo que no tiene control sobre el código fuente a.

2 tiene el problema de que dado que el conteo se almacena en otro objeto, si alguna vez tiene una situación que un puntero a un objeto contado de referencia existente se está convirtiendo en una referencia, es probable que tenga un error (IE, ya que el recuento no está en el objeto real, no hay forma de que la nueva referencia obtenga el recuento ... ref ref ref copia la construcción o la asignación está bien, porque pueden compartir el objeto de conteo, pero si alguna vez tiene que convertir desde un puntero, estás totalmente regado) ...

Ahora, como yo lo entiendo, boost :: shared_pointer usa el mecanismo 2, o algo así ... Dicho esto, no puedo decidirme qué ¡es peor! Solo he usado el mecanismo 1, en el código de producción ... ¿Alguien tiene experiencia con ambos estilos? ¿O tal vez hay otra forma mejor que ambas?

Respuesta

27

"¿Cuál es la mejor manera de implementar punteros inteligentes en C++"

  1. No! Utilice un puntero inteligente existente, bien probado, como boost :: shared_ptr o std :: tr1 :: shared_ptr (std :: unique_ptr y std :: shared_ptr con C++ 11)
  2. Si tiene que hacerlo, recuerde:
    1. uso seguro-bool idioma
    2. proporcionar una operator->
    3. proporcionan la garantía fuerte excepción
    4. documento los requisitos de excepción de su clase hace en el Deleter
    5. uso copiar-modificar-swap cuando sea posible para implementar la fuerte excepción gua Rantee documento
    6. si usted maneja multithreading correctamente
    7. escritura extensa unidad de prueba
    8. implementar la conversión a la base de tal manera que va a eliminar en el tipo de puntero derivados (punteros inteligentes policied/Deleter dinámica punteros inteligentes)
    9. apoyo para tener acceso a puntero prima
    10. considerar el costo/beneficio de un proporcionando punteros débiles para romper los ciclos
    11. proporcionar a los operadores de fundición apropiadas para sus punteros inteligentes
    12. hacer su constructor con plantilla para manejar construir puntero base desde derivado.

Y no se olvide nada de lo que haya olvidado en la lista incompleta anteriormente.

7

He usado boost :: shared_ptr desde hace varios años y, si bien tiene razón en cuanto a la desventaja (no es posible realizar asignaciones mediante el puntero), creo que definitivamente valió la pena debido a la gran cantidad de errores relacionados con el puntero me salvó de.

En mi motor de juego homebrew he reemplazado punteros normales con shared_ptr tanto como sea posible. El rendimiento alcanzado por esta causa no es tan malo si está llamando a la mayoría de las funciones por referencia para que el compilador no tenga que crear demasiadas instancias temporales shared_ptr.

+1

Además, la versión reforzada de shared_ptr se ha migrado a TR1 y, con el tiempo, será una biblioteca estándar de C++. –

+1

Una comparación de rendimiento de impulsores inteligentes está aquí: http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/smarttests.htm –

1

El problema con 2 se puede evitar. Boost ofrece boost :: shared_from_this por la misma razón. En la práctica, no es un gran problema.

Pero la razón por la que fueron con su opción # 2 es que puede ser utilizado en todos los casos. Confiar en la herencia no siempre es una opción, y luego te queda un puntero inteligente que no puedes usar para la mitad de tu código.

Tendría que decir # 2 es mejor, simplemente porque puede ser utilizado en cualquier circunstancia.

3

Boost también tiene un puntero intrusiva (como solución 1), que no requiere que hereda de nada. Requiere cambiar el puntero a la clase para almacenar el recuento de referencias y proporcionar funciones de miembro adecuadas. Lo he usado en casos en los que la eficiencia de la memoria era importante y no quería que se utilizara la sobrecarga de otro objeto para cada puntero compartido.

Ejemplo:

class Event { 
public: 
typedef boost::intrusive_ptr<Event> Ptr; 
void addRef(); 
unsigned release(); 
\\ ... 
private: 
unsigned fRefCount; 
}; 

inline void Event::addRef() 
{ 
    fRefCount++; 
} 
inline unsigned Event::release(){ 
    fRefCount--; 
    return fRefCount; 
} 

inline void intrusive_ptr_add_ref(Event* e) 
{ 
    e->addRef(); 
} 

inline void intrusive_ptr_release(Event* e) 
{ 
    if (e->release() == 0) 
    delete e; 
} 

El typedef Ptr se utiliza para que pueda fácilmente switcth entre el impulso :: shared_ptr <> e impulsar :: intrusive_ptr <> sin cambiar ningún código de cliente

3

Si usted se pega con los que están en la biblioteca estándar, estarás bien.
Aunque hay algunos otros tipos que los que ha especificado.

  • compartido: Cuando la propiedad es compartida entre varios objetos
  • Propiedad: Cuando un objeto posee el objeto, pero se permite la transferencia.
  • Unmovable: Donde un objeto posee el objeto y no se puede transferir.

La biblioteca estándar tiene:

  • std :: auto_ptr

Boost tiene un par más de haber sido adaptado por TR1 (próxima versión de la norma)

  • std :: tr1 :: shared_ptr
  • std :: tr1 :: weak_ptr

Y los que aún están en aumento (que en relativamente debe tener de todos modos) que con suerte se convertirán en tr2.

  • impulso :: scoped_ptr
  • impulso :: scoped_array
  • impulso :: shared_array
  • impulso :: intrusive_ptr

Ver: Smart Pointers: Or who owns you baby?

+0

No creo que boost :: scoped_ptr lo haya incorporado tr1, por lo que sigue siendo boost :: scoped_ptr, no std :: tr1 :: scoped_ptr. –

+0

Upps. Mi error. –

2

Me parece este la pregunta es como preguntar "¿Cuál es el mejor algoritmo de clasificación?" No hay una respuesta, depende de tus circunstancias.

Para mis propios fines, estoy usando su tipo 1. No tengo acceso a la biblioteca TR1. Tengo control completo sobre todas las clases para las que necesito compartir punteros. La memoria adicional y la eficiencia de tiempo del tipo 1 pueden ser bastante leves, pero el uso de la memoria y la velocidad son grandes problemas para mi código, por lo que el tipo 1 fue muy difícil.

Por otro lado, para cualquiera que pueda usar TR1, creo que la clase de tipo 2 std :: tr1 :: shared_ptr sería una opción sensata por defecto, que se utilizará siempre que no exista alguna razón urgente para usarlo

9

Sólo para suministrar una visión diferente a la respuesta Boost ubicua (a pesar de que es la respuesta correcta para muchos usos), echar un vistazo a Loki 's ejecución de punteros inteligentes. Para un discurso sobre la filosofía del diseño, el creador original de Loki escribió el libro Modern C++ Design.

+0

+1 ya que boost no proporciona la opción deep_copy para sus punteros inteligentes y creo que es una pena. – n1ckp

1

Nuestro proyecto utiliza punteros inteligentes ampliamente. Al principio no había certeza sobre qué puntero usar, por lo que uno de los autores principales eligió un puntero intrusivo en su módulo y el otro una versión no intrusiva.

En general, las diferencias entre los dos tipos de puntero no fueron significativas. La única excepción es que las primeras versiones de nuestra puntero no intrusiva implícitamente convertido de un puntero prima y esto puede conducir fácilmente a problemas de memoria si los punteros se utilizan de forma incorrecta:

void doSomething (NIPtr<int> const &); 

void foo() { 
    NIPtr<int> i = new int; 
    int & j = *i; 
    doSomething (&j);   // Ooops - owned by two pointers! :(
} 

Hace un tiempo, algunos refactorización resultó en algunos partes del código que se fusionan, por lo que debe hacerse una elección sobre qué tipo de puntero usar. El puntero no intrusivo ahora tenía el constructor de conversión declarado como explícito y por lo tanto se decidió ir con el puntero intrusivo para ahorrar en la cantidad de cambio de código que se requería.

Para nuestra gran sorpresa, una cosa que notamos fue que tuvimos una mejora inmediata en el rendimiento al usar el puntero intrusivo. No investigamos mucho sobre esto, y simplemente asumimos que la diferencia era el costo de mantener el objeto de recuento. Es posible que otras implementaciones de punteros compartidos no intrusivos ya hayan resuelto este problema.

+0

Es por eso que he comenzado a escribir un puntero inteligente en cada clase que se utilizará con un puntero inteligente. Entonces la decisión se puede tomar por clase y cambiar sin afectar el código del cliente. Esto solo funciona mientras los dos tipos de puntero tengan la misma interfaz. – KeithB

1

Lo que estamos hablando son intrusiva y no intrusivos punteros inteligentes. Boost tiene ambos. boost::intrusive_ptr llama a una función para disminuir y aumentar el recuento de referencias de su objeto, cada vez que necesite cambiar el recuento de referencias. No está llamando a funciones miembro, sino a funciones gratuitas. Por lo tanto, permite administrar objetos sin la necesidad de cambiar la definición de sus tipos. Y como usted dice, boost::shared_ptr no es intrusivo, su categoría 2.

Tengo una respuesta que explique intrusive_ptr: Making shared_ptr not use delete. En resumen, lo usa si tiene un objeto que ya tiene recuento de referencia, o necesita (como explica) un objeto al que ya se hace referencia como propiedad de un intrusive_ptr.

Cuestiones relacionadas