Suche…


Syntax

  • std::shared_ptr<ClassType> variableName = std::make_shared<ClassType>(arg1, arg2, ...);
  • std::shared_ptr<ClassType> variableName (new ClassType(arg1, arg2, ...));
  • std::unique_ptr<ClassType> variableName = std::make_unique<ClassType>(arg1, arg2, ...); // C ++ 14
  • std::unique_ptr<ClassType> variableName (new ClassType(arg1, arg2, ...));

Bemerkungen

C ++ ist keine speicherverwaltete Sprache. Dynamisch zugewiesener Speicher (dh Objekte, die mit new ) wird "durchgesickert", wenn er nicht explizit freigegeben wird (mit delete ). Es liegt in der Verantwortung des Programmierers, sicherzustellen, dass der dynamisch zugewiesene Speicher freigegeben wird, bevor der letzte Zeiger auf das Objekt gelöscht wird.

Mithilfe von intelligenten Zeigern kann der Umfang des dynamisch zugewiesenen Speichers automatisch verwaltet werden (dh, wenn der letzte Zeigerverweis seinen Gültigkeitsbereich verlässt).

Intelligente Zeiger werden in den meisten Fällen gegenüber "rohen" Zeigern bevorzugt. Sie machen die Eigentümersemantik dynamisch zugewiesenen Speichers explizit, indem sie in ihren Namen mitteilen, ob ein Objekt gemeinsam genutzt werden soll oder in eindeutigem Besitz ist.

Verwenden Sie #include <memory> , um intelligente Zeiger verwenden zu können.

Eigentum teilen (std :: shared_ptr)

Die Klassenvorlage std::shared_ptr definiert einen gemeinsamen Zeiger, der den Besitz eines Objekts mit anderen gemeinsam genutzten Zeigern teilen kann. Dies steht im Gegensatz zu std::unique_ptr das ausschließliches Eigentum darstellt.

Das Freigabeverhalten wird durch eine als Referenzzählung bekannte Technik implementiert, bei der die Anzahl der gemeinsam genutzten Zeiger, die auf das Objekt zeigen, daneben gespeichert wird. Wenn diese Anzahl null ist, entweder durch die Zerstörung oder Neuzuweisung der letzten Instanz von std::shared_ptr , wird das Objekt automatisch zerstört.


// Creation: 'firstShared' is a shared pointer for a new instance of 'Foo'
std::shared_ptr<Foo> firstShared = std::make_shared<Foo>(/*args*/);

Um mehrere intelligente Zeiger zu erstellen, die dasselbe Objekt verwenden, müssen Sie einen anderen shared_ptr erstellen, der den ersten gemeinsam genutzten Zeiger aliasisiert. Hier gibt es zwei Möglichkeiten:

std::shared_ptr<Foo> secondShared(firstShared);  // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared;                      // 2nd way: Assigning

Mit secondShared eine der secondShared einem gemeinsamen Zeiger, der den Besitz unserer Instanz von Foo mit firstShared .

Der intelligente Zeiger funktioniert wie ein reiner Zeiger. Das heißt, Sie können sie mit * dereferenzieren. Der reguläre -> Operator funktioniert ebenfalls:

secondShared->test(); // Calls Foo::test()

Wenn der letzte Aliasname shared_ptr Gültigkeitsbereich verlässt, wird der Destruktor unserer Foo Instanz aufgerufen.

Warnung: Beim bad_alloc eines shared_ptr kann eine bad_alloc Ausnahme bad_alloc wenn zusätzliche Daten für die Semantik des gemeinsam genutzten Besitzes zugewiesen werden müssen. Wenn dem Konstruktor ein regulärer Zeiger übergeben wird, wird davon ausgegangen, dass er das Objekt besitzt, auf das er zeigt, und ruft den Deleter auf, wenn eine Ausnahme ausgelöst wird. Dies bedeutet, dass shared_ptr<T>(new T(args)) kein T Objekt shared_ptr<T> wenn die Zuweisung von shared_ptr<T> fehlschlägt. Es ist jedoch ratsam, make_shared<T>(args) oder allocate_shared<T>(alloc, args) , wodurch die Implementierung die Speicherzuordnung optimieren kann.


Zuweisen von Arrays ([]) mithilfe von shared_ptr

C ++ 11 C ++ 17

Leider gibt es keine direkte Möglichkeit, Arrays mithilfe von make_shared<> .

Es ist möglich, Arrays für shared_ptr<> mit new und std::default_delete .

Wenn Sie beispielsweise ein Array mit 10 Ganzzahlen zuordnen möchten, können Sie den Code als schreiben

shared_ptr<int> sh(new int[10], std::default_delete<int[]>());

Die Angabe von std::default_delete ist hier zwingend erforderlich, um sicherzustellen, dass der zugewiesene Speicher mithilfe von delete[] ordnungsgemäß bereinigt wird.

Wenn wir die Größe zur Kompilierzeit kennen, können wir dies folgendermaßen tun:

template<class Arr>
struct shared_array_maker {};
template<class T, std::size_t N>
struct shared_array_maker<T[N]> {
  std::shared_ptr<T> operator()const{
    auto r = std::make_shared<std::array<T,N>>();
    if (!r) return {};
    return {r.data(), r};
  }
};
template<class Arr>
auto make_shared_array()
-> decltype( shared_array_maker<Arr>{}() )
{ return shared_array_maker<Arr>{}(); }

dann gibt make_shared_array<int[10]> ein shared_ptr<int> das auf 10 Ints (alle standardmäßig erstellten Werte) verweist.

C ++ 17

Mit C ++ 17 erhielt shared_ptr spezielle Unterstützung für Array-Typen. Es ist nicht mehr erforderlich, den Array-Deleter explizit anzugeben, und der gemeinsam genutzte Zeiger kann mit dem Indexoperator [] dereferenziert werden:

std::shared_ptr<int[]> sh(new int[10]);
sh[0] = 42;

Gemeinsame Zeiger können auf ein Unterobjekt des Objekts zeigen, das sie besitzt:

struct Foo { int x; };
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<int> p2(p1, &p1->x);

Sowohl p2 als auch p1 besitzen das Objekt vom Typ Foo , aber p2 zeigt auf das int x . Das bedeutet, dass, wenn p1 Gültigkeitsbereich verlässt oder neu zugewiesen wird, das zugrunde liegende Foo Objekt noch aktiv ist, um sicherzustellen, dass p2 nicht baumelt.


Wichtig: Ein shared_ptr kennt nur sich selbst und alle anderen shared_ptr , die mit dem Alias-Konstruktor erstellt wurden. Es kennt keine anderen Zeiger, einschließlich aller anderen shared_ptr s, die mit einem Verweis auf dieselbe Foo Instanz erstellt wurden:

Foo *foo = new Foo;
std::shared_ptr<Foo> shared1(foo);
std::shared_ptr<Foo> shared2(foo); // don't do this

shared1.reset(); // this will delete foo, since shared1
                 // was the only shared_ptr that owned it

shared2->test(); // UNDEFINED BEHAVIOR: shared2's foo has been
                 // deleted already!!

Eigentumsübertragung von shared_ptr

In der Standardeinstellung erhöht shared_ptr die Referenzanzahl und überträgt den Besitz nicht. Es kann jedoch auch die Übertragung des Eigentums mit std::move :

shared_ptr<int> up = make_shared<int>();
// Transferring the ownership
shared_ptr<int> up2 = move(up);
// At this point, the reference count of up = 0 and the
// ownership of the pointer is solely with up2 with reference count = 1

Teilen mit temporärem Besitz (std :: weak_ptr)

Instanzen von std::weak_ptr können auf Objekte verweisen, deren Instanzen von std::shared_ptr während sie selbst nur temporäre Besitzer werden. Dies bedeutet, dass schwache Zeiger den Referenzzähler des Objekts nicht ändern und daher das Löschen eines Objekts nicht verhindern, wenn alle gemeinsam genutzten Zeiger des Objekts neu zugewiesen oder zerstört werden.


Im folgenden Beispiel werden Instanzen von std::weak_ptr verwendet, damit die Zerstörung eines std::weak_ptr nicht verhindert wird:

#include <memory>
#include <vector>

struct TreeNode {
    std::weak_ptr<TreeNode> parent;
    std::vector< std::shared_ptr<TreeNode> > children;
};

int main() {
    // Create a TreeNode to serve as the root/parent.
    std::shared_ptr<TreeNode> root(new TreeNode);

    // Give the parent 100 child nodes.
    for (size_t i = 0; i < 100; ++i) {
        std::shared_ptr<TreeNode> child(new TreeNode);
        root->children.push_back(child);
        child->parent = root;
    }

    // Reset the root shared pointer, destroying the root object, and
    // subsequently its child nodes.
    root.reset();
}

Wenn std::weak_ptr Knoten den std::weak_ptr Knoten des std::weak_ptr hinzugefügt werden, wird das parent std::weak_ptr auf den std::weak_ptr gesetzt. Das parent Element wird im Gegensatz zu einem gemeinsam genutzten Zeiger als schwacher Zeiger deklariert, sodass der Referenzzähler des Stammknotens nicht erhöht wird. Wenn der Wurzelknoten am Ende von main() , wird der Stamm zerstört. Da die einzigen noch verbliebenen std::shared_ptr Verweise auf die untergeordneten Knoten wurden in der Stammsammlung enthalten sind children , werden alle untergeordneten Knoten anschließend auch zerstört.

Aufgrund der Details der Steuerblockimplementierung kann der gemeinsam weak_ptr Speicher möglicherweise erst freigegeben werden, wenn der shared_ptr Referenzzähler und der weak_ptr Referenzzähler beide Null erreichen.

#include <memory>
int main()
{
    {
         std::weak_ptr<int> wk;
         {
             // std::make_shared is optimized by allocating only once 
             // while std::shared_ptr<int>(new int(42)) allocates twice.
             // Drawback of std::make_shared is that control block is tied to our integer
             std::shared_ptr<int> sh = std::make_shared<int>(42);
             wk = sh;
             // sh memory should be released at this point...
         }
         // ... but wk is still alive and needs access to control block
     }
     // now memory is released (sh and wk)
}

Da std::weak_ptr das referenzierte Objekt nicht am Leben std::weak_ptr ist ein direkter Datenzugriff über ein std::weak_ptr nicht möglich. Stattdessen wird eine lock() std::shared_ptr , die versucht, ein std::shared_ptr für das referenzierte Objekt abzurufen:

#include <cassert>
#include <memory>
int main()
{
    {
         std::weak_ptr<int> wk;
         std::shared_ptr<int> sp;
         {
             std::shared_ptr<int> sh = std::make_shared<int>(42);
             wk = sh;
             // calling lock will create a shared_ptr to the object referenced by wk
             sp = wk.lock();
             // sh will be destroyed after this point, but sp is still alive
         }
         // sp still keeps the data alive.
         // At this point we could even call lock() again 
         // to retrieve another shared_ptr to the same data from wk
         assert(*sp == 42);
         assert(!wk.expired());
         // resetting sp will delete the data,
         // as it is currently the last shared_ptr with ownership
         sp.reset();
         // attempting to lock wk now will return an empty shared_ptr,
         // as the data has already been deleted
         sp = wk.lock();
         assert(!sp);
         assert(wk.expired());
     }
}

Eindeutiger Besitz (std :: unique_ptr)

C ++ 11

Ein std::unique_ptr ist eine Klassenvorlage, die die Lebensdauer eines dynamisch gespeicherten Objekts verwaltet. Im Gegensatz zu std::shared_ptr gehört das dynamische Objekt jederzeit nur einer Instanz von std::unique_ptr .


// Creates a dynamic int with value of 20 owned by a unique pointer
std::unique_ptr<int> ptr = std::make_unique<int>(20);

(Hinweis: std::unique_ptr ist seit C ++ 11 und std::make_unique seit C ++ 14 verfügbar.)

Nur die Variable ptr enthält einen Zeiger auf ein dynamisch zugewiesenes int . Wenn ein eindeutiger Zeiger, der ein Objekt besitzt, seinen Gültigkeitsbereich verlässt, wird das eigene Objekt gelöscht, dh der Destruktor wird aufgerufen, wenn das Objekt vom Klassentyp ist und der Speicher für dieses Objekt freigegeben wird.

Um std::unique_ptr und std::make_unique mit Array-Typen zu verwenden, verwenden Sie deren Array-Spezialisierungen:

// Creates a unique_ptr to an int with value 59
std::unique_ptr<int> ptr = std::make_unique<int>(59);

// Creates a unique_ptr to an array of 15 ints
std::unique_ptr<int[]> ptr = std::make_unique<int[]>(15);

Sie können auf den std::unique_ptr wie auf einen reinen Zeiger zugreifen, da diese Operatoren überladen werden.


Sie können den Besitz des Inhalts eines intelligenten Zeigers auf einen anderen Zeiger übertragen, indem Sie std::move , wodurch der ursprüngliche intelligente Zeiger auf nullptr .

// 1. std::unique_ptr
std::unique_ptr<int> ptr = std::make_unique<int>();

// Change value to 1
*ptr = 1;

// 2. std::unique_ptr (by moving 'ptr' to 'ptr2', 'ptr' doesn't own the object anymore)
std::unique_ptr<int> ptr2 = std::move(ptr);

int a = *ptr2; // 'a' is 1
int b = *ptr;  // undefined behavior! 'ptr' is 'nullptr'
               // (because of the move command above)

unique_ptr an Funktionen als Parameter übergeben:

void foo(std::unique_ptr<int> ptr)
{
    // Your code goes here
}

std::unique_ptr<int> ptr = std::make_unique<int>(59);
foo(std::move(ptr))

unique_ptr von Funktionen zurückgeben. Dies ist die bevorzugte C ++ 11-Methode zum Schreiben von Factory-Funktionen, da sie eindeutig die Besitzersemantik der Rückgabe vermittelt: Der Aufrufer besitzt das resultierende unique_ptr und ist dafür verantwortlich.

std::unique_ptr<int> foo()
{
    std::unique_ptr<int> ptr = std::make_unique<int>(59);
    return ptr;
}

std::unique_ptr<int> ptr = foo();

Vergleichen Sie dies mit:

int* foo_cpp03();

int* p = foo_cpp03(); // do I own p? do I have to delete it at some point?
                      // it's not readily apparent what the answer is.
C ++ 14

Die Klassenvorlage make_unique wird seit C ++ 14 bereitgestellt. Es ist einfach, es manuell zu C ++ 11-Code hinzuzufügen:

template<typename T, typename... Args>
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(Args&&... args)
{ return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); }

// Use make_unique for arrays
template<typename T>
typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(size_t n)
{ return std::unique_ptr<T>(new typename std::remove_extent<T>::type[n]()); }
C ++ 11

Im Gegensatz zum dummen intelligenten Zeiger ( std::auto_ptr ) kann unique_ptr auch mit Vektorzuordnung ( nicht std::vector ) instanziiert werden. Frühere Beispiele waren für Skalarzuweisungen . Wenn Sie beispielsweise ein dynamisch zugewiesenes Integer-Array für 10 Elemente haben möchten, geben Sie als Vorlagentyp int[] (und nicht nur als int ):

std::unique_ptr<int[]> arr_ptr = std::make_unique<int[]>(10);

Was kann vereinfacht werden mit:

auto arr_ptr = std::make_unique<int[]>(10);

Jetzt verwenden Sie arr_ptr als wäre es ein Array:

arr_ptr[2] =  10; // Modify third element

Sie müssen sich keine Gedanken über die Aufteilung machen. Diese vorlagenspezifische Version ruft Konstruktoren und Destruktoren entsprechend auf. Die Verwendung einer vektorisierten Version von unique_ptr oder eines vector selbst ist eine persönliche Entscheidung.

In Versionen vor C ++ 11 war std::auto_ptr verfügbar. Im Gegensatz zu unique_ptr es erlaubt, auto_ptr s zu kopieren, wobei der Quell- ptr den Besitz des enthaltenen Zeigers verliert und das Ziel ihn erhält.

Verwenden von benutzerdefinierten Deleters, um einen Wrapper für eine C-Schnittstelle zu erstellen

Viele C-Schnittstellen wie SDL2 verfügen über eigene Löschfunktionen . Das bedeutet, dass Sie intelligente Zeiger nicht direkt verwenden können:

std::unique_ptr<SDL_Surface> a; // won't work, UNSAFE!

Stattdessen müssen Sie Ihren eigenen Deleter definieren. Die Beispiele hier verwenden die SDL_Surface Struktur, die mit der Funktion SDL_FreeSurface() freigegeben werden sollte, sie sollten jedoch an viele andere C-Schnittstellen SDL_FreeSurface() werden können.

Der Deleter muss mit einem Zeigerargument aufrufbar sein und kann daher zB ein einfacher Funktionszeiger sein:

std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);

Jedes andere aufrufbare Objekt funktioniert ebenfalls, beispielsweise eine Klasse mit einem operator() :

struct SurfaceDeleter {
    void operator()(SDL_Surface* surf) {
        SDL_FreeSurface(surf);
    }
};

std::unique_ptr<SDL_Surface, SurfaceDeleter> a(pointer, SurfaceDeleter{}); // safe
std::unique_ptr<SDL_Surface, SurfaceDeleter> b(pointer); // equivalent to the above
                                                         // as the deleter is value-initialized

Dadurch erhalten Sie nicht nur eine automatische, automatische Speicherverwaltung, die keinen Overhead erfordert (wenn Sie unique_ptr ), sondern auch die Sicherheit von Ausnahmen.

Beachten Sie, dass der Deleter Teil des Typs für unique_ptr ist und die Implementierung die leere Basisoptimierung verwenden kann, um jegliche Größenänderung für leere benutzerdefinierte Deleter zu vermeiden. Während also std::unique_ptr<SDL_Surface, SurfaceDeleter> und std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> dasselbe Problem auf ähnliche Weise lösen, ist der vorherige Typ immer nur die Größe eines Zeigers letzterer Typ muss zwei Zeiger enthalten: sowohl SDL_Surface* als auch den Funktionszeiger! Wenn Sie benutzerdefinierte Deleter für freie Funktionen verwenden, ist es vorzuziehen, die Funktion in einem leeren Typ einzugeben.

In Fällen, in denen die Referenzzählung wichtig ist, kann anstelle von unique_ptr ein shared_ptr verwendet werden. Der shared_ptr speichert immer einen Deleter, wodurch der Typ des Deleters gelöscht wird, was in APIs nützlich sein kann. Die Nachteile der Verwendung von shared_ptr gegenüber unique_ptr sind höhere Speicherkosten für die Speicherung des Deleters und Leistungskosten für die Aufrechterhaltung der Referenzzählung.

// deleter required at construction time and is part of the type
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);

// deleter is only required at construction time, not part of the type
std::shared_ptr<SDL_Surface> b(pointer, SDL_FreeSurface); 
C ++ 17

Mit template auto können wir unsere benutzerdefinierten Deleter noch einfacher verpacken:

template <auto DeleteFn>
struct FunctionDeleter {
    template <class T>
    void operator()(T* ptr) {
        DeleteFn(ptr);
    }
};

template <class T, auto DeleteFn>
using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;

Bei dem das obige Beispiel ist einfach:

unique_ptr_deleter<SDL_Surface, SDL_FreeSurface> c(pointer);

Der Zweck von auto ist es, alle freien Funktionen zu behandeln, unabhängig davon, ob sie void (z. B. SDL_FreeSurface ) oder nicht (z. B. fclose ).

Einzigartiger Besitz ohne Umzugssemantik (auto_ptr)

C ++ 11

Hinweis: std::auto_ptr wurde in C ++ 11 nicht mehr unterstützt und wird in C ++ 17 entfernt. Sie sollten dies nur verwenden, wenn Sie C ++ 03 oder eine frühere Version verwenden müssen und vorsichtig sein möchten. Es wird empfohlen, zu unique_ptr in Kombination mit std::move zu std::move , um std::auto_ptr Verhalten von std::auto_ptr zu ersetzen.

Bevor wir std::unique_ptr , bevor wir die Semantik des Verschiebens hatten, hatten wir std::auto_ptr . std::auto_ptr bietet einen eindeutigen Besitz, überträgt jedoch den Besitz beim Kopieren.

Wie bei allen intelligenten Zeigern bereinigt std::auto_ptr automatisch Ressourcen (siehe RAII ):

{
    std::auto_ptr<int> p(new int(42));
    std::cout << *p;
} // p is deleted here, no memory leaked

erlaubt aber nur einen Besitzer:

std::auto_ptr<X> px = ...;
std::auto_ptr<X> py = px; 
  // px is now empty 

Dies ermöglicht die Verwendung von std :: auto_ptr, um den Besitz explizit und eindeutig zu halten, da die Gefahr besteht, dass der Besitz unbeabsichtigt verloren geht:

void f(std::auto_ptr<X> ) {
    // assumes ownership of X
    // deletes it at end of scope
};

std::auto_ptr<X> px = ...;
f(px); // f acquires ownership of underlying X
       // px is now empty
px->foo(); // NPE!
// px.~auto_ptr() does NOT delete

Die Eigentumsübertragung erfolgte im Konstruktor "copy". auto_ptr und der auto_ptr ihre Operanden anhand einer nicht const Referenz, sodass sie geändert werden können. Eine Beispielimplementierung könnte sein:

template <typename T>
class auto_ptr {
    T* ptr;
public:
    auto_ptr(auto_ptr& rhs)
    : ptr(rhs.release())
    { }

    auto_ptr& operator=(auto_ptr& rhs) {
        reset(rhs.release());
        return *this;
    }

    T* release() {
        T* tmp = ptr;
        ptr = nullptr;
        return tmp;
    }

    void reset(T* tmp = nullptr) {
        if (ptr != tmp) {
            delete ptr;
            ptr = tmp;
        }
    }

    /* other functions ... */
};

Dies unterbricht die Kopiersemantik, bei der Sie beim Kopieren eines Objekts zwei gleichwertige Versionen hinterlassen müssen. Für jeden kopierbaren Typ T sollte ich schreiben können:

T a = ...;
T b(a);
assert(b == a);

Bei auto_ptr ist dies jedoch nicht der Fall. Daher ist es nicht sicher, auto_ptr s in Containern auto_ptr .

Get ein shared_ptr, der sich darauf bezieht

enable_shared_from_this können Sie eine gültige shared_ptr Instanz auf this enable_shared_from_this .

Indem Sie Ihre Klasse von der Klassenvorlage enable_shared_from_this , erben Sie eine Methode shared_from_this , die eine shared_ptr Instanz an this zurückgibt.

Beachten Sie, dass das Objekt an erster Stelle als shared_ptr erstellt werden muss:

#include <memory>
class A: public enable_shared_from_this<A> {
};
A* ap1 =new A();
shared_ptr<A> ap2(ap1); // First prepare a shared pointer to the object and hold it!
// Then get a shared pointer to the object from the object itself
shared_ptr<A> ap3 = ap1->shared_from_this(); 
int c3 =ap3.use_count(); // =2: pointing to the same object

Hinweis (2) Sie können enable_shared_from_this innerhalb des Konstruktors aufrufen.

#include <memory> // enable_shared_from_this

class Widget : public std::enable_shared_from_this< Widget >
{
public:
    void DoSomething()
    {
        std::shared_ptr< Widget > self = shared_from_this();
        someEvent -> Register( self );
    }
private:
    ...
};

int main()
{
    ...
    auto w = std::make_shared< Widget >();
    w -> DoSomething();
    ...
}

Wenn Sie shared_from_this() für ein Objekt verwenden, das nicht zu einem shared_ptr , z. B. ein lokales automatisches Objekt oder ein globales Objekt, ist das Verhalten undefiniert. Seit C ++ 17 wird stattdessen std::bad_alloc .

Die Verwendung von shared_from_this() von einem Konstruktor ist gleichbedeutend mit der Verwendung für ein Objekt, das sich nicht im Besitz eines shared_ptr , da die Objekte nach der Rückgabe des Konstruktors im Besitz des shared_ptr .

Casting von std :: shared_ptr-Zeigern

Es ist nicht möglich, static_cast , const_cast , dynamic_cast und reinterpret_cast direkt für std::shared_ptr zu verwenden, um einen Zeiger abzurufen, der den Besitz des als Argument übergebenen Zeigers teilt. Stattdessen sollten die Funktionen std::static_pointer_cast , std::const_pointer_cast , std::dynamic_pointer_cast und std::reinterpret_pointer_cast verwendet werden:

struct Base { virtual ~Base() noexcept {}; };
struct Derived: Base {};
auto derivedPtr(std::make_shared<Derived>());
auto basePtr(std::static_pointer_cast<Base>(derivedPtr));
auto constBasePtr(std::const_pointer_cast<Base const>(basePtr));
auto constDerivedPtr(std::dynamic_pointer_cast<Derived const>(constBasePtr));

Beachten Sie, dass std::reinterpret_pointer_cast in C ++ 11 und C ++ 14 nicht verfügbar ist, da es nur von N3920 vorgeschlagen und im Februar 2014 in Library Fundamentals TS übernommen wurde . Es kann jedoch wie folgt implementiert werden:

template <typename To, typename From>
inline std::shared_ptr<To> reinterpret_pointer_cast(
    std::shared_ptr<From> const & ptr) noexcept
{ return std::shared_ptr<To>(ptr, reinterpret_cast<To *>(ptr.get())); }

Einen intelligenten Zeiger schreiben: value_ptr

Ein value_ptr ist ein intelligenter Zeiger, der sich wie ein Wert verhält. Beim Kopieren wird der Inhalt kopiert. Wenn erstellt, erstellt es seinen Inhalt.

// Like std::default_delete:
template<class T>
struct default_copier {
  // a copier must handle a null T const* in and return null:
  T* operator()(T const* tin)const {
    if (!tin) return nullptr;
    return new T(*tin);
  }
  void operator()(void* dest, T const* tin)const {
    if (!tin) return;
    return new(dest) T(*tin);
  }
};
// tag class to handle empty case:
struct empty_ptr_t {};
constexpr empty_ptr_t empty_ptr{};
// the value pointer type itself:
template<class T, class Copier=default_copier<T>, class Deleter=std::default_delete<T>,
  class Base=std::unique_ptr<T, Deleter>
>
struct value_ptr:Base, private Copier {
  using copier_type=Copier;
  // also typedefs from unique_ptr

  using Base::Base;

  value_ptr( T const& t ):
    Base( std::make_unique<T>(t) ),
    Copier()
  {}
  value_ptr( T && t ):
    Base( std::make_unique<T>(std::move(t)) ),
    Copier()
  {}
  // almost-never-empty:
      value_ptr():
    Base( std::make_unique<T>() ),
    Copier()
  {}
  value_ptr( empty_ptr_t ) {}

  value_ptr( Base b, Copier c={} ):
    Base(std::move(b)),
    Copier(std::move(c))
  {}

  Copier const& get_copier() const {
    return *this;
  }

  value_ptr clone() const {
    return {
      Base(
        get_copier()(this->get()),
        this->get_deleter()
      ),
      get_copier()
    };
  }
  value_ptr(value_ptr&&)=default;
  value_ptr& operator=(value_ptr&&)=default;

  value_ptr(value_ptr const& o):value_ptr(o.clone()) {}
  value_ptr& operator=(value_ptr const&o) {
    if (o && *this) {
      // if we are both non-null, assign contents:
      **this = *o;
    } else {
      // otherwise, assign a clone (which could itself be null):
      *this = o.clone();
    }
    return *this;
  }
  value_ptr& operator=( T const& t ) {
    if (*this) {
      **this = t;
    } else {
      *this = value_ptr(t);
    }
    return *this;
  }
  value_ptr& operator=( T && t ) {
    if (*this) {
      **this = std::move(t);
    } else {
      *this = value_ptr(std::move(t));
    }
    return *this;
  }
  T& get() { return **this; }
  T const& get() const { return **this; }
  T* get_pointer() {
    if (!*this) return nullptr;
    return std::addressof(get());
  }
  T const* get_pointer() const {
    if (!*this) return nullptr;
    return std::addressof(get());
  }
  // operator-> from unique_ptr
};
template<class T, class...Args>
value_ptr<T> make_value_ptr( Args&&... args ) {
  return {std::make_unique<T>(std::forward<Args>(args)...)};
}

Dieses bestimmte value_ptr ist nur leer, wenn Sie es mit empty_ptr_t oder wenn Sie es verlassen. Es macht die Tatsache unique_ptr , dass es sich um ein unique_ptr , so dass der explicit operator bool() const funktioniert. .get() wurde geändert, um einen Verweis zurückzugeben (da er fast nie leer ist), und .get_pointer() gibt stattdessen einen Zeiger zurück.

Dieser intelligente Zeiger kann in pImpl Fällen nützlich pImpl , in denen pImpl wird, der Inhalt von pImpl außerhalb der Implementierungsdatei pImpl werden soll.

Mit einem nicht standardmäßigen Copier kann er sogar virtuelle Basisklassen verarbeiten, die wissen, wie Instanzen ihrer abgeleiteten Objekte erzeugt und in Werttypen umgewandelt werden.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow