¿Cuál es la diferencia entre un std :: shared_ptr vacío y uno nulo en C ++?

80

La página cplusplus.comshared_ptr hace una distinción entre un vacío std::shared_ptr y un nulo shared_ptr . La página cppreference.com no menciona explícitamente la distinción, pero usa tanto "vacío" como comparación nullptren su descripción del std::shared_ptrcomportamiento.

¿Hay alguna diferencia entre un vacío y un nulo shared_ptr? ¿Existe algún caso de uso para tales punteros de comportamiento mixto? ¿Tiene sentido un nulo no vacío shared_ptr? ¿Habría alguna vez un caso en el uso normal (es decir, si no construyó uno explícitamente) en el que podría terminar con un vacío pero no nulo shared_ptr?

¿Y alguna de estas respuestas cambia si está usando la versión Boost en lugar de la versión C ++ 11?

RM
fuente

Respuestas:

80

Es un extraño rincón del shared_ptrcomportamiento. Tiene un constructor que te permite hacer un shared_ptrque posea algo y apunte a otra cosa:

template< class Y > 
shared_ptr( const shared_ptr<Y>& r, T *ptr );

El shared_ptrconstruido usando este constructor comparte la propiedad con r, pero apunta a lo que sea ​​que ptrapunta (es decir, llamar get()o operator->()volverá ptr). Esto es útil para los casos en los que ptrapunta a un subobjeto (por ejemplo, un miembro de datos) del objeto propiedad de r.

La página que vinculó llama a shared_ptrque no posee nada vacío y a shared_ptrque no apunta a nada (es decir, de quién get() == nullptr) nulo . ( En este sentido, el estándar usa vacío ; nulo no lo es). Puede construir un valor nulo pero no vacío shared_ptr, pero no será muy útil. Un vacío pero no nulo shared_ptres esencialmente un puntero no propietario, que se puede usar para hacer algunas cosas extrañas como pasar un puntero a algo asignado en la pila a una función que espera unshared_ptr (pero sugiero golpear a quien haya puesto shared_ptrdentro la API primero).

boost::shared_ptrtambién tiene este constructor , al que llaman constructor de alias .

TC
fuente
8
Cabe destacar: C ++ 11 § 20.7.2.2.1 (p16) "Nota: Este constructor permite la creación de una shared_ptrinstancia vacía con un puntero almacenado no NULL". También vale la pena mencionar la nota anterior (p15), "Para evitar la posibilidad de un puntero colgando, el usuario de este constructor debe asegurarse de que psigue siendo válido al menos hasta que rse destruya el grupo de propiedad de ". De hecho, una construcción poco utilizada.
WhozCraig
@Cubbi A shared_ptrcuyos get()rendimientos nullptr se comparan igual a nullptrindependientemente de si posee algo.
TC
3
Un nulo, pero no vacío- shared_ptrs pueden ser útiles, para asegurarse de alguna función es ejecutada una vez que todos los punteros que poseen ejecutan fuera de alcance (incluso en caso de una excepción!). No estoy seguro de si ahora hay una clase especial para esto.
Coldfix
@coldfix ¿Qué puede hacer un nulo pero no vacío shared_ptrque no shared_ptrpueda hacer un no nulo y no vacío ?
TC
2
El constructor de alias se origina en Bloomberg y se propuso para el estándar antes de que se implementara en Boost (consulte N1851 ). Prefiero el término estándar "comparte la propiedad con r" a la frase "posee todo lo que rposee"
Jonathan Wakely
9

¿Hay alguna diferencia entre un shared_ptr vacío y uno nulo?

Empty shared_ptrno tiene bloque de control y su recuento de uso se considera 0. La copia de empty shared_ptres otro vacío shared_ptr. Ambos son correos shared_ptrelectrónicos separados que no comparten un bloque de control común porque no lo tienen. Empty shared_ptrse puede construir con el constructor predeterminado o con el constructor que toma nullptr.

El nulo no vacío shared_ptrtiene un bloque de control que se puede compartir con otros shared_ptrs. La copia de nulo no vacío shared_ptres shared_ptrque comparte el mismo bloque de control que el original, shared_ptrpor lo que el recuento de uso no es 0. Se puede decir que todas las copias de shared_ptrcomparten lo mismonullptr . Se shared_ptrpuede construir un nulo no vacío con un puntero nulo del tipo de objeto (no nullptr)

He aquí un ejemplo:

#include <iostream>
#include <memory>

int main()
{
    std::cout << "std::shared_ptr<int> ptr1:" << std::endl;
    {
        std::shared_ptr<int> ptr1;
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;

    std::cout << "std::shared_ptr<int> ptr1(nullptr):" << std::endl;
    {
        std::shared_ptr<int> ptr1(nullptr);
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;

    std::cout << "std::shared_ptr<int> ptr1(static_cast<int*>(nullptr))" << std::endl;
    {
        std::shared_ptr<int> ptr1(static_cast<int*>(nullptr));
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;

    return 0;
}

Produce:

std::shared_ptr<int> ptr1:
    use count before copying ptr: 0
    use count  after copying ptr: 0
    ptr1 is null

std::shared_ptr<int> ptr1(nullptr):
    use count before copying ptr: 0
    use count  after copying ptr: 0
    ptr1 is null

std::shared_ptr<int> ptr1(static_cast<int*>(nullptr))
    use count before copying ptr: 1
    use count  after copying ptr: 2
    ptr1 is null

http://coliru.stacked-crooked.com/a/54f59730905ed2ff

anton_rh
fuente
1
Creo que esto responde mejor a por qué tenemos que buscar nulos en el borrador personalizado de shared_ptr. ¿Tiene sentido buscar nullptr en el eliminador personalizado de shared_ptr?
David Lee