¿Cómo se implementa el RPP std::tr1::shared?


He estado pensando en usar punteros compartidos, y sé cómo implementar uno yo mismo't No quiero hacerlo, así que estoy tratando std::tr1::shared_ptr, y tengo un par de preguntas...

¿Cómo se implementa el conteo de referencias? ¿Utiliza una lista doblemente vinculada? (Por cierto, ya he buscado en Google, pero no puedo encontrar nada confiable.)

¿Hay alguna trampa para usar el std::tr1::shared_ptr?

Author: Christian Rau, 2012-02-09

4 answers

shared_ptr debe gestionar un contador de referencia y el transporte de un funtor deleter que se deduce por el tipo del objeto dado en la inicialización.

La clase shared_ptr normalmente alberga dos miembros: a T* (que es devuelto por operator-> y desreferenciado en operator*) y a aux* donde aux es una clase abstracta interna que contiene: {[27]]}

  • un contador (incrementado / decrementado al copiar-asignar / destruir)
  • lo que sea necesario para hacer incremento / decremento atómico (no necesario si la plataforma específica atomic INC / DEC está disponible)
  • un resumen virtual destroy()=0;
  • un destructor virtual.

Tal clase aux (el nombre real depende de la implementación) se deriva de una familia de clases templatizadas (parametrizadas en el tipo dado por el constructor explícito, digamos U derivado de T), que agregan:

  • un puntero al objeto (igual que T*, pero con el tipo real: esto es necesario para administrar correctamente todos los casos de T ser una base para lo que sea U tener múltiples T en la jerarquía de derivación)
  • una copia del objeto deletor dado como política de eliminación al constructor explícito (o el predeterminado deletor simplemente haciendo delete p, donde p es el U* anterior)
  • la anulación del método destroy, llamando al funtor deleter.

Un boceto simplificado puede ser este:

template<class T>
class shared_ptr
{
    struct aux
    {
        unsigned count;

        aux() :count(1) {}
        virtual void destroy()=0;
        virtual ~aux() {} //must be polymorphic
    };

    template<class U, class Deleter>
    struct auximpl: public aux
    {
        U* p;
        Deleter d;

        auximpl(U* pu, Deleter x) :p(pu), d(x) {}
        virtual void destroy() { d(p); } 
    };

    template<class U>
    struct default_deleter
    {
        void operator()(U* p) const { delete p; }
    };

    aux* pa;
    T* pt;

    void inc() { if(pa) interlocked_inc(pa->count); }

    void dec() 
    { 
        if(pa && !interlocked_dec(pa->count)) 
        {  pa->destroy(); delete pa; }
    }

public:

    shared_ptr() :pa(), pt() {}

    template<class U, class Deleter>
    shared_ptr(U* pu, Deleter d) :pa(new auximpl<U,Deleter>(pu,d)), pt(pu) {}

    template<class U>
    explicit shared_ptr(U* pu) :pa(new auximpl<U,default_deleter<U> >(pu,default_deleter<U>())), pt(pu) {}

    shared_ptr(const shared_ptr& s) :pa(s.pa), pt(s.pt) { inc(); }

    template<class U>
    shared_ptr(const shared_ptr<U>& s) :pa(s.pa), pt(s.pt) { inc(); }

    ~shared_ptr() { dec(); }

    shared_ptr& operator=(const shared_ptr& s)
    {
        if(this!=&s)
        {
            dec();
            pa = s.pa; pt=s.pt;
            inc();
        }        
        return *this;
    }

    T* operator->() const { return pt; }
    T& operator*() const { return *pt; }
};

Donde weak_ptr se requiere interoperabilidad un segundo contador (weak_count) es requerido en aux (será incrementado / decrementado por weak_ptr), y delete pa debe suceder solo cuando ambos contadores alcanzan cero.

 46
Author: Emilio Garavaglia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-28 05:29:12

¿Cómo se implementa el conteo de referencias?

Una implementación de puntero inteligente podría ser deconstruida, usando diseño de clase basado en políticas1, en:

  • Política de almacenamiento

  • Política De Propiedad

  • Política de conversión

  • Política de comprobación

Incluidos como parámetros de plantilla. Las estrategias populares de propiedad incluyen: copia profunda, conteo de referencias, enlace de referencias, y una copia destructiva.

El conteo de referencias rastrea el número de punteros inteligentes que apuntan a (poseer2) el mismo objeto. Cuando el número va a cero, el objeto puntiagudo se elimina3. El contador real podría ser:

  1. Compartido entre objetos smart pointer, donde cada puntero inteligente contiene un puntero al contador de referencia:

introduzca la descripción de la imagen aquí

  1. Incluido solo en una estructura adicional que agrega un nivel adicional de indirection el objeto puntiagudo. Aquí el espacio sobre la cabeza de sostener un contador en cada puntero inteligente se intercambia con una velocidad de acceso más lenta:

introduzca la descripción de la imagen aquí

  1. Contenido dentro del objeto pointee: conteo de referencias intrusivo. La desventaja es que el objeto debe ser construido a priori con facilidades para contar:

    introduzca la descripción de la imagen aquí

  2. Finalmente el método en su pregunta, recuento de referencia usando doblemente vinculado las listas se llaman enlaces de referencia y es:

...[1] se basa en la observación de que realmente no necesita el recuento real de objetos de puntero inteligente que apuntan a un objeto puntiagudo; solo necesita detectar cuando ese recuento desciende a cero. Esto lleva a la idea de mantener una "lista de propiedad":

introduzca la descripción de la imagen aquí

La ventaja del enlace de referencia sobre el conteo de referencias es que el primero no usa tienda gratuita adicional, lo que hace que es más confiable: la creación de un puntero inteligente vinculado a una referencia no puede fallar. La desventaja es ese enlace de referencia necesita más memoria para su contabilidad (tres punteros frente a solo un puntero más un entero). Además, el conteo de referencias debería ser un poco más rápido: cuando copia punteros inteligentes, solo se necesita una indirecta y un incremento. La gestión de la lista es un poco más elaborada. En conclusión, debe usar enlaces de referencia solo cuando la tienda gratuita sea escasa. De lo contrario, prefiere conteo de referencias.

Con respecto a su segunda pregunta:

¿Utiliza (std::shared_ptr) una lista doblemente vinculada?

Todo lo que pude encontrar en el estándar de C++ fue:

20.7.2.2.6 creación de shared_ptr
...
7. [Nota: Estas funciones normalmente asignarán más memoria que sizeof(T) para permitir estructuras internas de contabilidad como los recuentos de referencia. -nota final ]

Que, en mi opinión, excluye listas doblemente enlazadas, ya que no contienen recuento real.

Su tercera pregunta:

¿Hay alguna trampa para usar el std::shared_ptr?

Gestión de referencias el conteo o la vinculación es una víctima de la fuga de recursos conocida como referencia cíclica. Vamos a tener un objeto A que tiene un puntero inteligente a un objeto B. También, el objeto B tiene un puntero inteligente a A. Estos dos objetos forman una referencia cíclica; aunque ya no uses ninguno de ellos, se usan el uno al otro. La estrategia de gestión de referencias no puede detectar tales referencias cíclicas, y los dos objetos permanecen asignados para siempre.

Debido a que la implementación de shared_ptr utiliza el conteo de referencias, las referencias cíclicas son potencialmente un problema. Una cadena cíclica shared_ptr se puede romper cambiando el código para que una de las referencias sea weak_ptr. Esto se hace asignando valores entre punteros compartidos y punteros débiles, pero un puntero débil no afecta el recuento de referencias. Si el único los punteros que apuntan a un objeto son débiles, el objeto se destruye.


1. Cada característica de diseño con múltiples implementaciones si se formula como política.

2. Punteros inteligentes de manera similar a los punteros que apuntan a un objeto asignado con new, no solo apuntan a ese objeto sino que también son responsables de su destrucción y con la liberación de la memoria que ocupa.

3. Sin más problemas, si no se utilizan otros punteros raw y / o punto a ella.

[1] Diseño C++ Moderno: Programación Genérica y Patrones de Diseño Aplicados. Andrei Alexandrescu, 01 de febrero de 2001

 17
Author: Ziezi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-04 23:37:32

¿Hay alguna trampa para usar el std::tr1::shared_ptr?

Sí, si crea ciclos en sus punteros de memoria compartida, entonces la memoria administrada por el puntero inteligente no se reciclará cuando el último puntero salga del alcance porque todavía hay referencias al puntero (es decir, los ciclos hacen que el recuento de referencias no descienda a cero).

Por ejemplo:

struct A
{
    std::shared_ptr<A> ptr;
};

std::shared_ptr<A> shrd_ptr_1 = std::make_shared(A());
std::shared_ptr<B> shrd_ptr_2 = std::make_shared(A());
shrd_ptr_1->ptr = shrd_ptr_2;
shrd_ptr_2->ptr = shrd_ptr_1;

Ahora, incluso si shrd_ptr_1 y shrd_ptr_2 salen del alcance, la memoria que están administrando no es reclamado porque el ptr miembro de cada uno están apuntando el uno al otro. Si bien este es un ejemplo muy ingenuo de este ciclo de memoria, puede, si utiliza este tipo de punteros sin ninguna disciplina, ocurrir de una manera mucho más nefasta y difícil de rastrear. Por ejemplo, podría ver dónde intentar implementar una lista enlazada circular donde cada next puntero es un std::shared_ptr, si no eres demasiado cuidadoso, podría resultar en problemas.

 3
Author: Jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-04 15:50:52

Si quieres ver todos los detalles sangrientos, puedes echar un vistazo a la implementación de boost shared_ptr:

Https://github.com/boostorg/smart_ptr

El conteo de referencia parece implementarse generalmente con un contador y instrucciones de incremento/decremento atómico específicas de la plataforma o bloqueo explícito con un mutex (consulte los archivos atomic_count_*.hpp en el espacio de nombres detail).

 3
Author: sth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-08 09:30:42