bibliotecas c++ design boost reference

bibliotecas - ¿Es mejor devolver una referencia de C++ o un weak_ptr?



boost c++ (9)

Supongamos que tengo una clase donde quiero que el usuario pueda tener una referencia a uno de mis miembros. ¿Cuál es preferido?

class Member; class ClassWithWeakPtr { private: boost::shared_ptr<Member> _member; public: boost::weak_ptr<Member> GetMember(); };

o

class Member; class ClassWithCppReference { private: Member _member; public: Member& GetMember() {return _member;} };

¿Qué piensas? ¿Cuándo es uno mejor que otro?


¿Por qué no devuelve un shared_ptr<> ? De esta forma, el cliente puede usar lo que devuelve todo el tiempo que lo necesita, pero no hay problema si la clase ''servidor'' se va.

No hay demasiadas situaciones donde la semántica de weak_ptr<> tiene mucho sentido (caches y ???). Por lo general, cuando un cliente solicita algo, quiere que la propiedad del mismo lo determine (y la propiedad compartida es tan buena como la plena propiedad).

Si tiene una situación en la que el objeto ''servidor'' puede destruirse sin conocimiento del cliente (una situación en la que podría querer use weak_ptr<> o shared_ptr<> ) lo peor que puede hacer es devolver una referencia a un miembro . En este caso, el cliente no puede saber si es seguro acceder a la referencia devuelta. Debe devolver una copia del miembro o un puntero inteligente que pueda administrar correctamente la vida del miembro que se devuelve.

Recuerde que en el caso donde hay una expresión que produce un ClassWithCppReference temporal (que no siempre es obvio), el cliente que llama a GetMember() ni siquiera podrá usar la referencia devuelta en la siguiente declaración.


Creo que la única respuesta razonable es que depende de cómo se relaciona el miembro con la clase y qué quiere que los usuarios de la clase puedan hacer. ¿Tiene _member una existencia significativa que sea independiente del objeto Class? Si no lo hace, no creo que usar un shared_ptr tenga sentido, ya sea que devuelva un weak_ptr o un shared_ptr . Esencialmente, cualquiera de los dos otorgaría acceso al usuario a un objeto miembro que podría sobrevivir al objeto Clase que le da significado. Eso podría evitar un bloqueo, pero a costa de ocultar un grave error de diseño.

Como lo indica awgn, debes ser muy cuidadoso al exponer tu clase interna. Pero creo que definitivamente hay un lugar para eso. Por ejemplo, tengo una clase en mi código que representa un objeto de archivo, compuesto por un objeto de encabezado de archivo y un objeto de datos de archivo. Duplicar completamente la interfaz del encabezado en la clase de archivo sería tonto y violar DRY. Podría, supongo, forzar al usuario a obtener una copia del objeto de encabezado, realizar los cambios en la copia y luego copiar el objeto externo de nuevo en la clase de archivo general. Pero eso introduce una gran cantidad de ineficiencia que solo le permite a usted hacer que la representación del objeto de archivo del encabezado sea diferente a la representación del objeto del encabezado. Si está seguro de que no es algo que quiera hacer, también puede devolver una referencia no const al encabezado, es más simple y más eficiente.


Debe evitar regalar sus partes internas; es la guía n. 42 de "Estándares de codificación C ++" (Herb Sutter y Andrei Alexandrescu). Si por alguna razón tiene que hacerlo, mejor devolver una referencia constante y no un puntero porque la constness no se propaga a través de ella.

weak_ptr <> parece ser una solución viable, incluso si su propósito básico es evitar ciclos de shared_ptr. En cambio, si devuelve un shared_ptr <> extiende la vida de tal internal que en la mayoría de los casos no tiene sentido.

El problema con la instancia que desaparece mientras alguien maneja una referencia a sus partes internas debe enfrentarse a una sincronización / comunicación correcta entre los hilos.


Mi guía básica e ignorante:

Me doy cuenta de que esto último puede ser inseguro si la instancia de ClassWithCppReference puede desaparecer mientras alguien todavía tiene una referencia a un miembro. Sin embargo, también puedo ver un argumento para la última clase con tipos de POD, como por ejemplo, un mensaje para un dispositivo, y no desea copiar el miembro todo el tiempo.


Dependiendo del contexto, cualquiera podría estar bien. El problema principal al devolver un enlace ''en vivo'' a un miembro (si tiene que exponer uno en primer lugar) es que quien usa el miembro expuesto es que su cliente puede retenerlo más de lo que existe el objeto que lo contiene. Y si su cliente accede a dicho miembro a través de una referencia cuando el objeto que lo contiene queda fuera del alcance, se enfrentará a bloqueos "extraños", valores incorrectos y una diversión similar. No recomendado.

Devolver el weak_ptr <> tiene la gran ventaja de poder comunicarle al cliente que el objeto al que intenta acceder ha desaparecido, lo que la referencia no puede hacer.

Mi valor de 2 centavos sería:

  • Si ninguno de sus clientes alguna vez usaría el miembro, usted como autor es la única persona que lo usa y controla la vida útil del objeto, devolver una referencia está bien. La referencia de Const sería incluso mejor, pero eso no siempre es posible en el mundo real con el código existente.
  • Si alguien más accede y usa el miembro, especialmente si está escribiendo una biblioteca, devuelva el archivo weak_ptr <>. Le ahorrará mucho dolor y facilitará la depuración.
  • No devolvería un shared_ptr <>. He visto este enfoque y generalmente es favorecido por personas que se sienten incómodas con el concepto de una referencia weak_ptr / weak. La desventaja principal que veo con esto es que extenderá artificialmente la vida útil del miembro de otro objeto más allá del alcance de su objeto contenedor. Eso es conceptualmente incorrecto en el 99% de los casos y, lo que es peor, puede convertir su error de programación (accediendo a algo que ya no existe) en un error conceptual (accediendo a algo que ya no debería estar allí).

¿Por qué devolver un puntero débil? Creo que lo estás haciendo más complicado sin ningún beneficio necesario.


Devolver un puntero débil definitivamente será más costoso y no tiene ningún propósito real: no puede aferrarse a la propiedad por más tiempo que la vida del objeto principal de todos modos.


En la mayoría de las situaciones, proporcionaría

const Member& GetMember() const; Member& GetMember(); // ideally, only if clients can modify it without // breaking any invariants of the Class weak_ptr< Member > GetMemberWptr(); // only if there is a specific need // for holding a weak pointer.

La razón detrás de esto es que (también yo y probablemente muchos otros) llamando a un método GetMember() implica que Class posee un member y, por lo tanto, la referencia devuelta solo será válida durante el tiempo de vida del objeto contenedor.

En la mayoría (pero no en todos) de los códigos que he encontrado, los métodos de GetMember() generalmente se usan para hacer cosas con el miembro devuelto de inmediato y no guardarlo para un usuario posterior. Después de todo, si necesita acceder al miembro, siempre puede solicitarlo desde el objeto que lo contiene en cualquier momento que lo necesite.


Quiero plantear algo en respuesta a los comentarios (de OP y Colomon, principalmente) sobre la eficiencia, etc. Muchas veces, copiar cosas en realidad no importa, en términos de rendimiento real.

He escrito programas que hacen mucha copia defensiva . Es una expresión idiomática en Java, donde, debido a que todos los objetos pasan por puntero, hay muchos alias, por lo tanto, copia todo lo que entre o salga de tu clase para romper el alias, asegurando que los clientes de tu clase no puedan violar invariantes de tu clase modificando un objeto después del hecho.

Los programas que he escrito han copiado a la defensiva estructuras enteras en lugares, incluidas listas y mapas completos. Solo para asegurarme de que el rendimiento no se vea afectado, perfilé extensamente el programa. Los principales cuellos de botella estaban en otra parte (e incluso entonces, sintonicé esos otros lugares para que el cuello de botella principal quedara en la red). En ninguna parte esta copia defensiva figura en los puntos calientes del programa.

ETA: Siento la necesidad de aclarar el punto de mi mensaje, ya que un comentarista lo leyó de manera diferente a como yo quería, y posiblemente otros también lo hayan hecho. Mi punto no es que está bien copiar cosas de cualquier manera; pero más bien, uno siempre debe perfilar el rendimiento de su código, en lugar de adivinar cómo funcionará.

A veces, cuando copiar estructuras enteras aún da un rendimiento aceptable, y al mismo tiempo hacer que el código sea más fácil de escribir, entonces, en mi opinión, es una mejor solución (en la mayoría de los casos) que el código que es ligeramente más rápido pero mucho más complicado.