C++
Puntatori intelligenti
Ricerca…
Sintassi
-
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, ...));
Osservazioni
Il C ++ non è un linguaggio gestito dalla memoria. La memoria allocata dinamicamente (cioè gli oggetti creati con new
) sarà "trapelata" se non è esplicitamente deallocata (con delete
). È responsabilità del programmatore assicurarsi che la memoria allocata dinamicamente venga liberata prima di scartare l'ultimo puntatore a quell'oggetto.
I puntatori intelligenti possono essere utilizzati per gestire automaticamente l'ambito della memoria allocata dinamicamente (ovvero quando l'ultimo riferimento del puntatore esce dall'ambito viene cancellato).
Puntatori intelligenti sono preferiti su puntatori "grezzi" nella maggior parte dei casi. Rendono esplicita la semantica della proprietà della memoria allocata dinamicamente, comunicando nel suo nome se un oggetto è destinato a essere condiviso o di proprietà esclusiva.
Usa #include <memory>
per poter usare puntatori intelligenti.
Condivisione della proprietà (std :: shared_ptr)
Il modello di classe std::shared_ptr
definisce un puntatore condiviso che è in grado di condividere la proprietà di un oggetto con altri puntatori condivisi. Questo contrasta con std::unique_ptr
che rappresenta la proprietà esclusiva.
Il comportamento di condivisione è implementato attraverso una tecnica nota come conteggio dei riferimenti, in cui il numero di puntatori condivisi che puntano all'oggetto è memorizzato accanto a esso. Quando questo conteggio raggiunge lo zero, attraverso la distruzione o la riassegnazione dell'ultima istanza di std::shared_ptr
, l'oggetto viene automaticamente distrutto.
// Creation: 'firstShared' is a shared pointer for a new instance of 'Foo'
std::shared_ptr<Foo> firstShared = std::make_shared<Foo>(/*args*/);
Per creare più puntatori intelligenti che condividono lo stesso oggetto, dobbiamo creare un altro shared_ptr
che alias il primo puntatore condiviso. Ecco due modi per farlo:
std::shared_ptr<Foo> secondShared(firstShared); // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared; // 2nd way: Assigning
Uno dei due modi sopra rende secondShared
un puntatore condiviso che condivide la proprietà della nostra istanza di Foo
con firstShared
.
Il puntatore intelligente funziona esattamente come un puntatore raw. Questo significa che puoi usare *
per dereferenziarli. Anche l'operatore regolare ->
funziona:
secondShared->test(); // Calls Foo::test()
Infine, quando l'ultimo alias shared_ptr
esce dall'ambito, viene chiamato il distruttore della nostra istanza di Foo
.
Avvertenza: la costruzione di un file shared_ptr
può generare un'eccezione bad_alloc
quando vengono bad_alloc
dati aggiuntivi per la semantica della proprietà condivisa. Se il costruttore riceve un puntatore regolare, assume di possedere l'oggetto puntato e chiama il deleter se viene lanciata un'eccezione. Ciò significa che shared_ptr<T>(new T(args))
non perderà un oggetto T
se l'allocazione di shared_ptr<T>
fallisce. Tuttavia, è consigliabile utilizzare make_shared<T>(args)
o allocate_shared<T>(alloc, args)
, che consentono all'implementazione di ottimizzare l'allocazione della memoria.
Allocazione di array ([]) utilizzando shared_ptr
Sfortunatamente, non esiste un modo diretto per allocare gli array usando make_shared<>
.
È possibile creare array per shared_ptr<>
usando new
e std::default_delete
.
Ad esempio, per allocare una matrice di 10 numeri interi, possiamo scrivere il codice come
shared_ptr<int> sh(new int[10], std::default_delete<int[]>());
La specifica di std::default_delete
è obbligatoria qui per assicurarti che la memoria allocata sia correttamente ripulita usando delete[]
.
Se conosciamo le dimensioni al momento della compilazione, possiamo farlo in questo modo:
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>{}(); }
quindi make_shared_array<int[10]>
restituisce un shared_ptr<int>
punta a 10 interi di default costruiti.
Con C ++ 17, shared_ptr
ottenuto un supporto speciale per i tipi di array. Non è più necessario specificare esplicitamente l'array-deleter e il puntatore condiviso può essere de-referenziato usando l'operatore []
dell'indice di array:
std::shared_ptr<int[]> sh(new int[10]);
sh[0] = 42;
I puntatori condivisi possono puntare a un sottooggetto dell'oggetto che possiede:
struct Foo { int x; };
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<int> p2(p1, &p1->x);
Sia p2
che p1
possiedono l'oggetto di tipo Foo
, ma p2
punta al suo membro int
x
. Ciò significa che se p1
esce dallo scope o viene riassegnato, l'oggetto Foo
sottostante sarà ancora vivo, assicurando che p2
non penzoli.
Importante: un shared_ptr
conosce solo se stesso e tutti gli altri shared_ptr
creati con il costruttore alias. Non conosce altri puntatori, inclusi tutti gli altri shared_ptr
creati con un riferimento alla stessa istanza di Foo
:
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!!
Trasferimento di proprietà di shared_ptr
Per impostazione predefinita, shared_ptr
incrementa il conteggio dei riferimenti e non trasferisce la proprietà. Tuttavia, può essere fatto per trasferire la proprietà usando 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
Condivisione con proprietà temporanea (std :: weak_ptr)
Le istanze di std::weak_ptr
possono puntare a oggetti di proprietà di istanze di std::shared_ptr
mentre diventano essi stessi solo proprietari temporanei. Ciò significa che i puntatori deboli non alterano il conteggio dei riferimenti dell'oggetto e quindi non impediscono la cancellazione di un oggetto se tutti i puntatori condivisi dell'oggetto sono riassegnati o distrutti.
Nel seguente esempio vengono utilizzate istanze di std::weak_ptr
modo che la distruzione di un oggetto ad albero non sia inibita:
#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();
}
Quando i nodi figli vengono aggiunti ai figli del nodo radice, il loro parent
membro std::weak_ptr
viene impostato sul nodo radice. Il membro parent
viene dichiarato come un puntatore debole anziché un puntatore condiviso, in modo che il conteggio dei riferimenti del nodo radice non venga incrementato. Quando il nodo root viene ripristinato alla fine di main()
, la root viene distrutta. Dal momento che i restanti solo std::shared_ptr
riferimenti ai nodi secondari erano contenuti nella collezione del radice children
, tutti i figli vengono successivamente distrutti pure.
A causa dei dettagli di implementazione del blocco di controllo, la memoria allocata shared_ptr potrebbe non essere rilasciata fino a shared_ptr
contatore di riferimento weak_ptr
e il weak_ptr
riferimento weak_ptr
entrambi raggiungono lo zero.
#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)
}
Poiché std::weak_ptr
non mantiene std::weak_ptr
suo oggetto di riferimento, l'accesso diretto ai dati tramite uno std::weak_ptr
non è possibile. Fornisce invece una funzione membro lock()
che tenta di recuperare un oggetto std::shared_ptr
sull'oggetto di riferimento:
#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());
}
}
Proprietà univoca (std :: unique_ptr)
Un std::unique_ptr
è un modello di classe che gestisce la durata di un oggetto archiviato in modo dinamico. A differenza di std::shared_ptr
, l'oggetto dinamico è di proprietà di una sola istanza di std::unique_ptr
in qualsiasi momento,
// Creates a dynamic int with value of 20 owned by a unique pointer
std::unique_ptr<int> ptr = std::make_unique<int>(20);
(Nota: std::unique_ptr
è disponibile da C ++ 11 e std::make_unique
da C ++ 14.)
Solo la variabile ptr
contiene un puntatore a un int
allocato dinamicamente. Quando un puntatore univoco che possiede un oggetto esce dall'ambito, l'oggetto di proprietà viene eliminato, ovvero viene chiamato il suo distruttore se l'oggetto è di tipo classe e viene rilasciata la memoria per quell'oggetto.
Per usare std::unique_ptr
e std::make_unique
con tipi di array, usa le loro specializzazioni di array:
// 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);
È possibile accedere a std::unique_ptr
proprio come un puntatore raw, perché sovraccarica quegli operatori.
È possibile trasferire la proprietà dei contenuti di un puntatore intelligente a un altro puntatore utilizzando std::move
, che farà in modo che il puntatore smart originale punti a 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)
Passando unique_ptr
a funzioni come parametro:
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))
Restituzione di unique_ptr
dalle funzioni. Questo è il metodo preferito di scrittura C ++ 11 per le funzioni di fabbrica, in quanto trasmette chiaramente la semantica della proprietà del unique_ptr
: il chiamante possiede il unique_ptr
risultante ed è responsabile di esso.
std::unique_ptr<int> foo()
{
std::unique_ptr<int> ptr = std::make_unique<int>(59);
return ptr;
}
std::unique_ptr<int> ptr = foo();
Confronta questo a:
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.
Il modello di classe make_unique
viene fornito dal C ++ 14. È facile aggiungerlo manualmente al codice C ++ 11:
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]()); }
A differenza del puntatore intelligente stupido ( std::auto_ptr
), unique_ptr
può anche essere istanziato con l'allocazione vettoriale ( non con std::vector
). Gli esempi precedenti riguardavano allocazioni scalari . Ad esempio, per disporre di un array intero allocato dinamicamente per 10 elementi, si specificherà int[]
come tipo di modello (e non solo int
):
std::unique_ptr<int[]> arr_ptr = std::make_unique<int[]>(10);
Quale può essere semplificato con:
auto arr_ptr = std::make_unique<int[]>(10);
Ora, usi arr_ptr
come se fosse un array:
arr_ptr[2] = 10; // Modify third element
Non devi preoccuparti della disallocazione. Questa versione specializzata del modello chiama appropriatamente costruttori e distruttori. L'uso della versione vettoriale di unique_ptr
o di un vector
stesso è una scelta personale.
Nelle versioni precedenti al C ++ 11, std::auto_ptr
era disponibile. A differenza di unique_ptr
, è consentito copiare auto_ptr
s, in base al quale il sorgente ptr
perderà la proprietà del puntatore contenuto e il target lo riceverà.
Utilizzare i deletatori personalizzati per creare un wrapper per un'interfaccia C
Molte interfacce C come SDL2 hanno le proprie funzioni di cancellazione. Ciò significa che non è possibile utilizzare direttamente i puntatori intelligenti:
std::unique_ptr<SDL_Surface> a; // won't work, UNSAFE!
Invece, è necessario definire il proprio deleter. Gli esempi qui usano la struttura SDL_Surface
che dovrebbe essere liberata usando la funzione SDL_FreeSurface()
, ma dovrebbero essere adattabili a molte altre interfacce C.
Il deleter deve essere richiamabile con un argomento pointer e, quindi, può essere ad esempio un semplice puntatore a funzione:
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);
Anche qualsiasi altro oggetto callable funzionerà, ad esempio una classe con un 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
Questo non solo ti offre una gestione della memoria automatica sicura, zero overhead (se usi unique_ptr
), ma ottieni anche un'eccezionale sicurezza.
Si noti che il deleter è parte del tipo per unique_ptr
e l'implementazione può utilizzare l' ottimizzazione di base vuota per evitare qualsiasi modifica delle dimensioni per i deletatori personalizzati vuoti. Quindi mentre std::unique_ptr<SDL_Surface, SurfaceDeleter>
e std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)>
risolvono lo stesso problema in modo simile, il primo tipo è ancora solo la dimensione di un puntatore mentre il quest'ultimo tipo deve contenere due puntatori: sia la SDL_Surface*
che il puntatore della funzione! Quando si dispone di funzioni di deleter personalizzate, è preferibile racchiudere la funzione in un tipo vuoto.
Nei casi in cui il conteggio dei riferimenti è importante, si potrebbe usare un shared_ptr
invece di un unique_ptr
. shared_ptr
memorizza sempre un deleter, questo cancella il tipo del deleter, che potrebbe essere utile nelle API. Gli svantaggi dell'utilizzo di shared_ptr
su unique_ptr
includono un costo di memoria più elevato per l'archiviazione del deleter e un costo delle prestazioni per il mantenimento del conteggio dei riferimenti.
// 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);
Con template auto
, possiamo rendere ancora più semplice il wrapping dei nostri delet personalizzati:
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>>;
Con il quale l'esempio sopra è semplicemente:
unique_ptr_deleter<SDL_Surface, SDL_FreeSurface> c(pointer);
Qui, lo scopo auto
è quello di gestire tutte le funzioni libere, sia che restituiscano void
(ad es. SDL_FreeSurface
) o meno (es. fclose
).
Proprietà unica senza spostamento della semantica (auto_ptr)
NOTA: std::auto_ptr
è stato deprecato in C ++ 11 e verrà rimosso in C ++ 17. Dovresti usarlo solo se sei costretto a usare C ++ 03 o prima e sei disposto a stare attento. Si consiglia di passare a unique_ptr in combinazione con std::move
per sostituire il comportamento di std::auto_ptr
.
Prima di avere std::unique_ptr
, prima di spostare la semantica, avevamo std::auto_ptr
. std::auto_ptr
fornisce una proprietà esclusiva ma trasferisce la proprietà sulla copia.
Come con tutti i puntatori intelligenti, std::auto_ptr
pulisce automaticamente le risorse (vedi RAII ):
{
std::auto_ptr<int> p(new int(42));
std::cout << *p;
} // p is deleted here, no memory leaked
ma consente solo un proprietario:
std::auto_ptr<X> px = ...;
std::auto_ptr<X> py = px;
// px is now empty
Questo permette di usare std :: auto_ptr per mantenere la proprietà esplicita e unica a rischio di perdere la proprietà non intenzionale:
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
Il trasferimento di proprietà è avvenuto nel costruttore "copia". Il costruttore di copie di auto_ptr
e l'operatore di assegnazione copia eseguono i loro operandi con riferimento non const
modo che possano essere modificati. Un esempio di implementazione potrebbe essere:
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 ... */
};
Questo interrompe la semantica della copia, che richiede che la copia di un oggetto ti lasci con due versioni equivalenti. Per qualsiasi tipo di copia, T
, dovrei essere in grado di scrivere:
T a = ...;
T b(a);
assert(b == a);
Ma per auto_ptr
, questo non è il caso. Di conseguenza, non è sicuro inserire auto_ptr
nei contenitori.
Ottenere un shared_ptr riferendosi a questo
enable_shared_from_this
ti permette di ottenere un'istanza shared_ptr
valida a this
.
Derivando la tua classe dal modello di classe enable_shared_from_this
, erediti un metodo shared_from_this
che restituisce un'istanza shared_ptr
a this
.
Si noti che l'oggetto deve essere creato come shared_ptr
al primo posto:
#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
Nota (2) non è possibile chiamare enable_shared_from_this
all'interno del costruttore.
#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();
...
}
Se si utilizza shared_from_this()
su un oggetto non di proprietà di shared_ptr
, come un oggetto automatico locale o un oggetto globale, il comportamento non è definito. Dal momento che C ++ 17 getta invece std::bad_alloc
.
Usare shared_from_this()
da un costruttore è equivalente a usarlo su un oggetto non di proprietà di un shared_ptr
, perché gli oggetti sono posseduti da shared_ptr
dopo il ritorno del costruttore.
Puntatori di casting std :: shared_ptr
Non è possibile utilizzare direttamente static_cast
, const_cast
, dynamic_cast
e reinterpret_cast
su std::shared_ptr
per recuperare un puntatore che condivide la proprietà con il puntatore passato come argomento. Devono invece essere utilizzate le funzioni std::static_pointer_cast
, std::const_pointer_cast
, std::dynamic_pointer_cast
e std::reinterpret_pointer_cast
:
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));
Si noti che std::reinterpret_pointer_cast
non è disponibile in C ++ 11 e C ++ 14, poiché è stato proposto solo da N3920 e adottato in Library Fundamentals TS a febbraio 2014 . Tuttavia, può essere implementato come segue:
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())); }
Scrivere un puntatore intelligente: value_ptr
Un value_ptr
è un puntatore intelligente che si comporta come un valore. Quando viene copiato, ne copia il contenuto. Quando viene creato, crea il suo contenuto.
// 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)...)};
}
Questo valore value_ptr è vuoto solo se lo costruisci con empty_ptr_t
o se ti sposti da esso. Espone il fatto che è un unique_ptr
, quindi explicit operator bool() const
lavora su di esso. .get()
è stato modificato per restituire un riferimento (poiché non è quasi mai vuoto) e .get_pointer()
restituisce invece un puntatore.
Questo puntatore intelligente può essere utile per i casi pImpl
, dove vogliamo semantica del valore ma non vogliamo nemmeno esporre il contenuto di pImpl
al di fuori del file di implementazione.
Con una Copier
non predefinita, può persino gestire classi di base virtuali che sanno come generare istanze delle loro derivate e trasformarle in tipi di valore.