Was ist der Unterschied zwischen einem leeren und einem null std :: shared_ptr in C ++?

80

Auf der Seite cplusplus.comshared_ptr wird zwischen einer leeren std::shared_ptr und einer Null unterschieden shared_ptr . Die Seite cppreference.com ruft die Unterscheidung nicht explizit auf, sondern verwendet nullptrin ihrer Beschreibung des std::shared_ptrVerhaltens sowohl "leer" als auch Vergleich mit .

Gibt es einen Unterschied zwischen einer leeren und einer Null shared_ptr? Gibt es einen Anwendungsfall für solche Zeiger mit gemischtem Verhalten? Ist eine nicht leere Null shared_ptrüberhaupt sinnvoll? Würde es im normalen Gebrauch jemals einen Fall geben (dh wenn Sie keinen explizit konstruiert hätten), in dem Sie am Ende eine leere, aber nicht null haben könnten shared_ptr?

Und ändert sich eine dieser Antworten, wenn Sie die Boost-Version anstelle der C ++ 11-Version verwenden?

RM
quelle

Antworten:

80

Es ist eine seltsame Ecke des shared_ptrVerhaltens. Es hat einen Konstruktor, der Sie eine machen können , shared_ptrdie besitzt etwas und zeigt auf etwas anderes:

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

Der shared_ptrkonstruierte mit diesem Konstruktor Aktien Eigentum mit r, sondern verweist auf was auch immer ptrPunkte zu (dh, Berufung get()oder operator->()kehrt ptr). Dies ist praktisch für Fälle, in denen ptrauf ein Unterobjekt (z. B. ein Datenelement) des Objekts verwiesen wird, dessen Eigentümer es ist r.

Die gewünschte Seite verlinkt sind Anrufe ein , shared_ptrdie nichts besitzt leer , und shared_ptrdass Punkte auf nichts (dh deren get() == nullptr) null . ( Leer wird in diesem Sinne vom Standard verwendet; null ist nicht.) Sie können eine Null shared_ptrerstellen, aber nicht leer , aber es ist nicht sehr nützlich. Eine leere, aber nicht null shared_ptrist im Wesentlichen ein nicht besitzender Zeiger, der verwendet werden kann, um einige seltsame Dinge zu tun, z. B. einen Zeiger auf etwas zu übergeben, das auf dem Stapel einer Funktion zugewiesen ist, die eine erwartetshared_ptr (aber ich würde vorschlagen, jeden zu schlagen, der shared_ptrhineingesteckt hat die API zuerst).

boost::shared_ptrauch hat diesen Konstruktor , die sie den Anruf Aliasing Konstruktor .

TC
quelle
8
Bemerkenswert: C ++ 11 § 20.7.2.2.1 (S. 16) "Hinweis: Dieser Konstruktor ermöglicht die Erstellung einer leeren shared_ptrInstanz mit einem nicht NULL gespeicherten Zeiger." Erwähnenswert ist auch der vorstehende Hinweis (S. 15): "Um die Möglichkeit eines baumelnden Zeigers zu vermeiden, muss der Benutzer dieses Konstruktors sicherstellen, dass dieser pmindestens so lange gültig bleibt, bis die Eigentümergruppe von rzerstört wird." Eine selten verwendete Konstruktion.
WhozCraig
@Cubbi A , shared_ptrderen get()Rendite nullptr nicht gleich zu vergleichen , um nullptrunabhängig davon , ob es irgendetwas besitzt.
TC
3
Ein null-aber-nicht-leeres shared_ptrs kann nützlich sein, um sicherzustellen, dass eine Funktion ausgeführt wird, sobald alle besitzenden Zeiger keinen Gültigkeitsbereich mehr haben (auch im Ausnahmefall!). Ich bin mir nicht sicher, ob es jetzt eine spezielle Klasse dafür gibt.
Coldfix
@coldfix Was kann ein Null-aber- shared_ptrNicht- Leerer tun, was ein Nicht-Null-und-Nicht- Leerer nicht shared_ptrkann?
TC
2
Der Aliasing-Konstruktor stammt aus Bloomberg und wurde für den Standard vorgeschlagen, bevor er in Boost implementiert wurde (siehe N1851 ). Ich bevorzuge den Standardbegriff "Aktienbesitz mit r" gegenüber der Formulierung "besitzt was auch immer rbesitzt"
Jonathan Wakely
9

Gibt es einen Unterschied zwischen einem leeren und einem null shared_ptr?

Leer shared_ptrhat keinen Steuerblock und seine Verwendungsanzahl wird als 0 betrachtet. Eine Kopie von leer shared_ptrist eine weitere leere shared_ptr. Sie sind beide separate shared_ptrs, die keinen gemeinsamen Steuerblock haben, weil sie ihn nicht haben. Leer shared_ptrkann mit dem Standardkonstruktor oder mit einem Konstruktor erstellt werden, der benötigt nullptr.

Nicht leere Null shared_ptrhat einen Steuerblock, der mit anderen shared_ptrs geteilt werden kann. Eine Kopie der nicht leeren Null shared_ptrhat shared_ptrdenselben Steuerblock wie das Original, shared_ptrsodass die Verwendungsanzahl nicht 0 ist. Es kann gesagt werden, dass alle Kopien shared_ptrdieselbenullptr Null haben . Nicht leere Null shared_ptrkann mit einem Nullzeiger vom Objekttyp (nicht nullptr) erstellt werden.

Hier ist ein Beispiel:

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

Es gibt aus:

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
quelle
1
Ich denke, dies ist eine bessere Antwort darauf, warum wir im benutzerdefinierten Deleter von shared_ptr nach null suchen müssen. Ist es sinnvoll, im benutzerdefinierten Deleter von shared_ptr nach nullptr zu suchen?
David Lee