2012-04-26 13 views
9

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 de T. *this será un subobjeto de un objeto t del tipo T. Debe haber al menos una instancia shared_ptrp 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, ....)

+1

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. –

+0

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'. –

+1

@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. –

Respuesta

4

Acepto que esto es un agujero en la especificación, por lo tanto, es un defecto. Básicamente es lo mismo que http://open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2179 aunque ese problema proviene de un ángulo ligeramente diferente (y en mi humilde opinión más roto).

No estoy seguro de que esté de acuerdo en que este es un uso indebido de shared_ptr, creo que está bien hacerlo con shared_ptrs, porque a diferencia del código en el número 2179, utiliza eliminadores no operativos. Creo que el problema es cuando intentas combinar ese tipo de uso de shared_ptr con enable_shared_from_this.

Así que mi primer pensamiento fue para solucionarlo mediante la ampliación de los requisitos de shared_from_this:

Requiere:enable_shared_from_this<T> habrá una clase base accesible de T. *this será un subobjeto de un objeto t del tipo T. Habrá por lo menos un shared_ptr ejemplo p que posee &ty cualquier otro shared_ptr instancias que son dueños de la propiedad &t compartirán con p.

Esto no es más que suficiente, sin embargo, debido a que su ejemplo cumple con ese requisito: en la segunda llamada a shared_from_this() sólo hay un propietario (p1), pero que ya ha "dañado" el estado de la clase de base enable_shared_from_this llamando al lock2().

una forma más pequeña del programa es:

#include <memory> 
using namespace std; 

int main() 
{ 
    struct X : public enable_shared_from_this<X> { }; 
    auto xraw = new X; 
    shared_ptr<X> xp1(xraw); // #1 
    { 
    shared_ptr<X> xp2(xraw, [](void*) { }); // #2 
    } 
    xraw->shared_from_this(); // #3 
} 

Los tres de libstdC++, libC++ y VC++ (Dinkumware) se comportan de la misma y tirar bad_weak_ptr en el # 3, ya que en el # 2 se ponen al día la weak_ptr<X> miembro la clase base para compartir la propiedad con xp2, que sale del alcance dejando el weak_ptr<X> en estado caducado.

Interesantemente boost::shared_ptr no tira, en su lugar # 2 es un no-operativo y # 3 devuelve un shared_ptr que comparte la propiedad con xp1. Esto se hizo en respuesta a un bug report con casi exactamente el mismo ejemplo que el anterior.

3

Sí, aquí hay un defecto en C++ 11. Al permitir esto:

Es un poco inusual tener dos objetos shared_ptr almacenan el mismo puntero & x pero no compartir la propiedad, y para usar "eliminadores" que no terminan la vida útil del * get(), pero nada lo prohíbe

esto se debe indicar explícitamente a ser un comportamiento indefinido, independientemente de lo que los "eliminadores" hacen. Claro, técnicamente no es ilegal hacer las cosas de esa manera.

Sin embargo, usted es mintiendo a las personas que usan el código.La expectativa de cualquiera que reciba un shared_ptr es que ahora tiene la propiedad del objeto. Mientras mantengan ese shared_ptr (o una copia del mismo), el objeto al que apunta seguirá existiendo.

Ese no es el caso con su código. Entonces diría que es sintácticamente correcto pero semánticamente inválido.

El lenguaje para shared_from_this está bien. Es el lenguaje para shared_ptr que necesita cambios. Debe indicar que es un comportamiento indefinido crear dos punteros únicos independientes que "posean" el mismo puntero.

+0

Es' shared_ptr p1 (nullptr, d1); shared_ptr p2 (nullptr, d2); '¿igual de inválido? – aschepler

+0

¿Qué sucede si le doy a 'x' la duración de almacenamiento estático y hago que el ctor y dtor de' X' sean privados? Entonces, realmente puedo garantizar que si tiene un 'shared_ptr ' no vacío, el objeto al que apunta todavía existe (porque siempre existe). – aschepler

+0

No intento derribar esto: creo que la idea básica sería una buena solución para el estándar, pero necesitaría una redacción más cuidadosa. – aschepler

Cuestiones relacionadas