Jaka jest różnica między pustym a null std :: shared_ptr w C ++?

80

Cplusplus.com shared_ptrstrona nazywa się rozróżnienie między pusty std::shared_ptr a wartość null shared_ptr . Strona cppreference.com nie wyjaśnia wyraźnie tego rozróżnienia, ale nullptrw opisie std::shared_ptrzachowania używa zarówno słowa „pusty”, jak i porównania .

Czy jest różnica między pustym a null shared_ptr? Czy jest jakiś przypadek użycia dla takich wskaźników o mieszanym zachowaniu? Czy niepusty null ma w shared_ptrogóle sens? Czy kiedykolwiek byłby przypadek w normalnym użyciu (tj. Gdybyś nie skonstruował go jawnie), w którym mógłbyś skończyć z pustym, ale niezerowym shared_ptr?

I czy któraś z tych odpowiedzi ulegnie zmianie, jeśli używasz wersji Boost zamiast wersji C ++ 11?

RM
źródło

Odpowiedzi:

80

To dziwny zakątek shared_ptrzachowania. Ma konstruktora, który pozwala zrobić shared_ptr, że posiada coś i wskazuje na coś innego:

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

shared_ptrWykonana przy użyciu tego konstruktora akcjonariatu, z r, ale punkty do dowolnych ptrpunktów na (czyli, nazywając get()lub operator->()zwróci ptr). Jest to przydatne w przypadkach, gdy ptrwskazuje na podobiekt (np. Element członkowski danych) obiektu, którego właścicielem jest r.

Strona, do shared_ptrktórej utworzyłeś łącze , wywołuje a, który nie posiada niczego , co jest puste , oraz a, shared_ptrktóre wskazuje na nic (tj. Czyje get() == nullptr) null . ( Pusty stosowany jest w tym sensie w normie; zerowy nie jest). Można skonstruować null ale-nie-pusty shared_ptr, ale to nie będzie bardzo przydatna. Pusty-ale-nie-null shared_ptrjest w zasadzie wskaźnikiem nieposiadającym, który może być użyty do zrobienia pewnych dziwnych rzeczy, takich jak przekazanie wskaźnika do czegoś przydzielonego na stosie do funkcji oczekującej ashared_ptr (ale sugerowałbym uderzenie każdego, kto umieścił shared_ptrwewnątrz API najpierw).

boost::shared_ptrma również ten konstruktor , który nazywają konstruktorem aliasingu .

TC
źródło
8
Warto zwrócić uwagę: C ++ 11 § 20.7.2.2.1 (p16) „Uwaga: ten konstruktor umożliwia utworzenie pustej shared_ptrinstancji z przechowywanym wskaźnikiem innym niż NULL”. Warto również wspomnieć o poprzedniej uwadze (s. 15), „Aby uniknąć możliwości wiszącego wskaźnika, użytkownik tego konstruktora musi zapewnić, że ppozostanie on ważny przynajmniej do momentu rzniszczenia grupy własności ”. Rzeczywiście rzadko używana konstrukcja.
WhozCraig
@Cubbi shared_ptrktórego get()powraca nullptr ma porównać równe nullptr, niezależnie od tego, czy jest właścicielem czegokolwiek.
TC,
3
Null-but-nonempty shared_ptrs może być przydatna, aby upewnić się, że jakaś funkcja zostanie wykonana, gdy wszystkie wskaźniki będące właścicielami skończą się poza zakresem (nawet w przypadku wyjątku!). Nie jestem pewien, czy istnieje teraz specjalna klasa do tego.
coldfix
@coldfix Co może zrobić wartość null-but-nonempty, czego nie może shared_ptrzrobić element niezerowy i niepusty shared_ptr?
TC,
2
Konstruktor aliasingu pochodzi z Bloomberga i został zaproponowany do standardu, zanim został zaimplementowany w Boost (patrz N1851 ). Wolę standardowy termin „współwłasność z r” od wyrażenia „ posiada wszystko, co rposiada”
Jonathan Wakely
9

Czy istnieje różnica między pustym a null shared_ptr?

Empty shared_ptrnie ma bloku kontrolnego, a jego licznik użycia uważany jest za 0. Kopia pustego shared_ptrto kolejny pusty shared_ptr. Oba są oddzielnymi shared_ptrelementami, które nie mają wspólnego bloku sterującego, ponieważ go nie mają. Empty shared_ptrmożna skonstruować za pomocą domyślnego konstruktora lub konstruktora, który pobiera nullptr.

Niepusty null shared_ptrma blok kontrolny, który może być współdzielony z innymi shared_ptrs. Kopia niepustej wartości null shared_ptrjest shared_ptrtaka, że ​​dzieli ten sam blok kontrolny co oryginał, shared_ptrwięc liczba użytych wartości nie jest równa 0. Można powiedzieć, że wszystkie kopie shared_ptrmają takie samenullptr . Niepusty null shared_ptrmożna skonstruować za pomocą pustego wskaźnika typu obiektu (nie nullptr)

Oto przykład:

#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;
}

Wyprowadza:

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
źródło
1
Myślę, że to lepiej wyjaśnia, dlaczego musimy sprawdzić wartość null w niestandardowym usuwaniu shared_ptr. Czy ma sens sprawdzanie wartości nullptr w niestandardowym usuwaniu shared_ptr?
David Lee,