Pregunta

He escrito una biblioteca que expone referencias a varios tipos de objetos relacionados. Todos estos objetos tienen sus vidas administradas internamente por la biblioteca a través de boost::shared_ptr

Un usuario de la biblioteca también podría saber, por naturaleza de la biblioteca, la vida útil de cualquiera de los objetos expuestos. Para que puedan almacenar punteros o mantener referencias a estos objetos. Sería razonable para ellos hacer esto y saber cuándo esos objetos ya no son válidos.

Pero me siento culpable de obligar a mis usuarios a ser razonables.

¿Es aceptable que una biblioteca exponga weak_ptr a sus objetos? ¿Han hecho esto otras bibliotecas?

He perfilado el uso de esta biblioteca en aplicaciones y he encontrado que es demasiado crítica para exponer <=> exclusivamente.

¿Sería más prudente que las funciones de API coincidentes expongan una referencia o un débil_ptr o que cualquier objeto sea capaz de exponer un <=> consigo mismo?

¿Fue útil?

Solución

Si los smart_ptr s ya están directamente accesibles para los usuarios de la biblioteca, entonces ya tienen acceso a los weak_ptr s, simplemente a través del constructor de shared_ptr correspondiente. Pero si los <=> s son internos a la biblioteca, esa es una historia diferente.

En ese caso, recomendaría dejar que cada objeto pase <=> s a sí mismo, además de cualquier otro acceso que ofrezca su biblioteca. Eso brinda a los usuarios la mayor flexibilidad: si necesitan un <=>, tienen acceso inmediato a él; si necesitan un <=>, pueden obtenerlo fácilmente; y si solo necesitan acceso al objeto en sí, pueden ignorar los punteros inteligentes por completo.

Por supuesto, no sé qué hace su biblioteca o cómo se usa o diseña. Eso podría cambiar mi recomendación.

Otros consejos

La aparición de mecanismos complicados para llegar a los objetos de su biblioteca solo dará como resultado que las personas no usen su biblioteca. Si la semántica de la biblioteca dicta que necesita que las personas usen débiles_ptrs, no hay forma de que el usuario sepa que los objetos pueden desaparecer en algún momento. Haga que la interfaz exprese tanta información sobre el uso de la biblioteca como sea posible, mantenga baja la documentación y la haga infinitamente más fácil de usar.

No puedes diseñar alrededor de usuarios malos / inexpertos.

Si le da a sus clientes acceso a weak_ptr s, simplemente pueden bloquearlos para crear shared_ptr sy terminar retrasando la destrucción de objetos. Eso podría causar problemas con su biblioteca.

Sugiero envolver un weak_ptr<T>::lock() en alguna otra clase y darle a la persona que llama un shared_ptr<InterfaceClass> a eso. De esa manera no pueden simplemente llamar a <=>. Parece que tiene restricciones de rendimiento que pueden influir en la forma en que lo implementa, pero un <=> podría ser una buena manera de hacerlo, y mantener la clase con el <=> interno en su biblioteca.

De esa manera, también mantiene estos detalles de implementación fuera de la interfaz de su biblioteca y puede cambiar la forma en que lo implementa sin cambiar su interfaz.

No veo ningún problema al exponer débiles_ptrs, especialmente dado que TR1 tiene punteros inteligentes similares (PDF).

TR1 es implementado en gran parte por Visual Studio y GCC, pero no por algunos de los otros compiladores que existen. Pero cuando se implementa en todos los compiladores que le interesan, es posible que desee volver a trabajar la API para exponer esos punteros inteligentes.

Si desea atrapar el uso no válido de la biblioteca (tratando de acceder a los objetos cuando se han eliminado), así como tener una API de alto rendimiento (no débil_ptr y shared_ptr's en la API), entonces podría considerar tener una API diferente para las versiones de depuración y no depuración.

Supongamos, por simplicidad, que solo tiene una clase de objetos que expone; llama a esta clase Object. El tipo de puntero que devuelve de la API para acceder a los objetos internos se define como:

#ifdef DEBUG
typedef ObjectPtrFacade ObjectPtr 
#else
typedef Object * ObjectPtr;
#endif

Aquí la fachada es la clase que escribes. Funciona más o menos así:

class ObjectPtrFacade {
public:
    ObjectPtrFacade(Object *o) : wptr(o) { }
    // copy constructor and assignment here etc. (not written)
    Object * operator -> () const { return access(); }
    Object & operator * () const { return *access(); }
private:
    Object * access() { 
      assert(wptr.use_count() > 0);
      return (Object *)(wptr.lock());
    }
    weak_ptr<Object> wptr; 
}

De esta manera, cada vez que crea una compilación de depuración, tiene un tipo especial de puntero inteligente en uso que afirma antes de acceder al objeto que use_count () es mayor que cero, es decir, que el objeto todavía existe. Si se ha liberado el objeto, obtendrá una afirmación errónea, que es mejor que una referencia de puntero nulo.

En general, por supuesto, es así que usar débil_ptr no ayuda si tienes " stupid " usuarios de la API, porque podrían llamar a lock () y luego hacer una referencia de puntero nulo después de que weak_ptr devuelva shared_ptr que está vacío ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top