Considere este programa:shared_ptr múltiple almacenar mismo puntero
#include <memory>
#include <iostream>
class X
: public std::enable_shared_from_this<X>
{
public:
struct Cleanup1 { void operator()(X*) const; };
struct Cleanup2 { void operator()(X*) const; };
std::shared_ptr<X> lock1();
std::shared_ptr<X> lock2();
};
std::shared_ptr<X> X::lock1()
{
std::cout << "Resource 1 locked" << std::endl;
return std::shared_ptr<X>(this, Cleanup1());
}
std::shared_ptr<X> X::lock2()
{
std::cout << "Resource 2 locked" << std::endl;
return std::shared_ptr<X>(this, Cleanup2());
}
void X::Cleanup1::operator()(X*) const
{
std::cout << "Resource 1 unlocked" << std::endl;
}
void X::Cleanup2::operator()(X*) const
{
std::cout << "Resource 2 unlocked" << std::endl;
}
int main()
{
std::cout << std::boolalpha;
X x;
std::shared_ptr<X> p1 = x.lock1();
{
std::shared_ptr<X> p2 = x.lock2();
}
}
no veo nada en la sección estándar de C++ 11 20.7.2 sugiriendo algo de esto es válido. Es un poco inusual tener dos objetos shared_ptr
que almacenen el mismo puntero &x
pero que no compartan la propiedad y que utilicen "modificadores" que no terminan la vida útil de *get()
, pero nada lo prohíbe. (Y si alguno de los que son totalmente involuntario, sería difícil explicar por qué algunos shared_ptr
funciones miembro aceptan un valor std::nullptr_t
.) Y como era de esperar, los resultados de los programas:
Resource 1 locked
Resource 2 locked
Resource 2 unlocked
Resource 1 unlocked
Pero ahora si añado un poco para main()
:
int main()
{
std::cout << std::boolalpha;
X x;
std::shared_ptr<X> p1 = x.lock1();
bool test1(x.shared_from_this());
std::cout << "x.shared_from_this() not empty: " << test1 << std::endl;
{
std::shared_ptr<X> p2 = x.lock2();
}
try {
bool test2(x.shared_from_this());
std::cout << "x.shared_from_this() not empty: " << test2 << std::endl;
} catch (std::exception& e) {
std::cout << "caught: " << e.what() << std::endl;
}
}
entonces las cosas se ponen más complicadas. Con g ++ 4.6.3, consigo la salida:
Resource 1 locked
x.shared_from_this() not empty: true
Resource 2 locked
Resource 2 unlocked
caught: std::bad_weak_ptr
Resource 1 unlocked
¿Por qué la segunda llamada a shared_from_this()
fallar? Se cumplen todos los requisitos de 20.7.2.4p7:
Requiere:
enable_shared_from_this<T>
habrá una clase base accesible deT
.*this
será un subobjeto de un objetot
del tipoT
. Debe haber al menos una instanciashared_ptr
p
que posee&t
.
[T
es X
, t
es x
, p
es p1
.]
Pero g ++ 's enable_shared_from_this
sigue esencialmente la implementación sugerida de la (no normativo) "Nota" en 20.7.2.4p10, utilizando una miembro privado weak_ptr
en la clase enable_shared_from_this
. Y parece imposible dar cuenta de este tipo de problema sin hacer algo considerablemente más complicado en enable_shared_from_this
.
¿Es esto un defecto en el estándar? (Si es así, aquí no se necesita ningún comentario sobre cuál debería ser la solución: agregue un requisito para que el programa de ejemplo invoque Comportamiento no definido, cambie la Nota para no sugerir que una implementación tan simple sería suficiente, ....)
Desde una perspectiva de los estándares, no estoy seguro. El motivo WHY es que la implementación de g ++ (y de impulso) espera que la primera vez que cree un puntero compartido a partir de una instancia de puntero sin formato de X será la única y la variable privada weak_ptr se configure para apuntar a esa instancia creada. Cuando crea un segundo puntero compartido nuevo en la misma instancia en 'lock2()', sobrescribe el weak_ptr original, y cuando se desbloquea, el puntero débil apunta ahora a nada, de ahí el error. –
La nota no normativa que demuestra una implementación de ejemplo de 'enable_from_this' concluye (en el párrafo 11) con" Los constructores shared_ptr que crean ** punteros únicos ** pueden detectar la presencia de una base 'enable_shared_from_this' y asignar el' shared_ptr' a su miembro '__weak_this'." [énfasis mío]. Me parece notable que esta nota no haya sido redactada con algo en el sentido de "los constructores que crean * poseer * punteros", y me pregunto qué es un único 'shared_ptr'. –
@Luc Creo que se está refiriendo a los constructores que después de la creación devolverían verdadero desde su 'unique()'. Básicamente, los constructores que toman la propiedad inicial de un puntero sin formato o un único_ptr. –