C++
Intelligente Zeiger
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
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.
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)
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.
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]()); }
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);
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)
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.