C++
Slimme wijzers
Zoeken…
Syntaxis
-
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, ...));
Opmerkingen
C ++ is geen door het geheugen beheerde taal. Dynamisch toegewezen geheugen (dat wil zeggen objecten gemaakt met new
) zal worden "gelekt" als het niet expliciet wordt toegewezen (met delete
). Het is de verantwoordelijkheid van de programmeur om ervoor te zorgen dat het dynamisch toegewezen geheugen wordt vrijgegeven voordat de laatste aanwijzer van dat object wordt weggegooid.
Slimme aanwijzers kunnen worden gebruikt om het bereik van dynamisch toegewezen geheugen automatisch te beheren (dwz wanneer de laatste verwijzing van de aanwijzer buiten bereik valt, wordt deze verwijderd).
Slimme aanwijzers hebben in de meeste gevallen de voorkeur boven "onbewerkte" aanwijzers. Ze maken de eigendomssemantiek van dynamisch toegewezen geheugen expliciet, door in hun naam te communiceren of een object bedoeld is om gedeeld te worden of uniek eigendom is.
Gebruik #include <memory>
om smart pointers te kunnen gebruiken.
Eigendom delen (std :: shared_ptr)
De std::shared_ptr
definieert een gedeelde aanwijzer die het eigendom van een object kan delen met andere gedeelde verwijzingen. Dit in tegenstelling tot std::unique_ptr
die exclusief eigendom vertegenwoordigt.
Het deelgedrag wordt geïmplementeerd via een techniek die bekend staat als referentietelling, waarbij het aantal gedeelde verwijzingen dat naar het object verwijst, ernaast wordt opgeslagen. Wanneer deze telling nul bereikt, hetzij door de vernietiging of de nieuwe toewijzing van de laatste std::shared_ptr
instantie, wordt het object automatisch vernietigd.
// Creation: 'firstShared' is a shared pointer for a new instance of 'Foo'
std::shared_ptr<Foo> firstShared = std::make_shared<Foo>(/*args*/);
Om meerdere slimme pointers te maken die hetzelfde object delen, moeten we nog een shared_ptr
die de eerste gedeelde aanwijzer aliassen. Hier zijn 2 manieren om het te doen:
std::shared_ptr<Foo> secondShared(firstShared); // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared; // 2nd way: Assigning
Op beide manieren is secondShared
een gedeelde aanwijzer die het eigendom van ons exemplaar van Foo
firstShared
met firstShared
.
De slimme aanwijzer werkt net als een onbewerkte aanwijzer. Dit betekent dat u *
kunt gebruiken om ze te verwijderen. De reguliere ->
operator werkt ook:
secondShared->test(); // Calls Foo::test()
Ten slotte, wanneer de laatste alias shared_ptr
buiten bereik valt, wordt de destructor van onze Foo
instantie genoemd.
Waarschuwing: het construeren van een shared_ptr
kan een uitzondering bad_alloc
wanneer extra gegevens voor semantiek voor gedeeld eigendom moeten worden toegewezen. Als de constructor een regelmatige aanwijzer wordt gepasseerd, neemt hij aan dat hij eigenaar is van het object waarnaar wordt verwezen en roept hij de deleter op als een uitzondering wordt gegenereerd. Dit betekent dat shared_ptr<T>(new T(args))
een T
object niet zal lekken als de toewijzing van shared_ptr<T>
mislukt. Het is echter raadzaam om make_shared<T>(args)
of allocate_shared<T>(alloc, args)
, waarmee de implementatie de geheugentoewijzing kan optimaliseren.
Arrays toewijzen ([]) met shared_ptr
Helaas is er geen directe manier om arrays toe te wijzen met make_shared<>
.
Het is mogelijk om arrays te maken voor shared_ptr<>
met behulp van new
en std::default_delete
.
Om bijvoorbeeld een array van 10 gehele getallen toe te wijzen, kunnen we de code schrijven als
shared_ptr<int> sh(new int[10], std::default_delete<int[]>());
Het opgeven van std::default_delete
is hier verplicht om ervoor te zorgen dat het toegewezen geheugen correct wordt opgeruimd met delete[]
.
Als we de grootte tijdens het compileren kennen, kunnen we het op deze manier doen:
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>{}(); }
make_shared_array<int[10]>
retourneert vervolgens een shared_ptr<int>
wijzend naar 10 shared_ptr<int>
alle standaard geconstrueerd.
Met C ++ 17 kreeg shared_ptr
speciale ondersteuning voor shared_ptr
. Het is niet langer nodig om de array-deleter expliciet op te geven en de gedeelde aanwijzer kan worden verwijderd met behulp van de []
array-indexoperator:
std::shared_ptr<int[]> sh(new int[10]);
sh[0] = 42;
Gedeelde aanwijzers kunnen verwijzen naar een subobject van het object waarvan het eigenaar is:
struct Foo { int x; };
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<int> p2(p1, &p1->x);
Zowel p2
als p1
bezitten het object van het type Foo
, maar p2
naar zijn int
lid x
. Dit betekent dat als p1
buiten bereik valt of opnieuw wordt toegewezen, het onderliggende Foo
object nog steeds in leven is, zodat p2
niet bengelt.
Belangrijk: een shared_ptr
kent alleen zichzelf en alle andere shared_ptr
die zijn gemaakt met de alias constructor. Het kent geen andere verwijzingen, inclusief alle andere shared_ptr
s gemaakt met een verwijzing naar dezelfde Foo
instantie:
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!!
Eigendomoverdracht van shared_ptr
Standaard shared_ptr
het aantal referenties en wordt het eigendom niet overgedragen. Het kan echter worden gemaakt om het eigendom over te dragen met 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
Delen met tijdelijk eigendom (std :: weak_ptr)
Instanties van std::weak_ptr
kunnen verwijzen naar objecten die eigendom zijn van instanties van std::shared_ptr
terwijl ze zelf tijdelijke eigenaar worden. Dit betekent dat zwakke aanwijzers de referentietelling van het object niet wijzigen en daarom niet voorkomen dat een object wordt verwijderd als alle gedeelde aanwijzers van het object opnieuw worden toegewezen of vernietigd.
In het volgende voorbeeld worden instanties van std::weak_ptr
gebruikt zodat de vernietiging van een std::weak_ptr
niet wordt verhinderd:
#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();
}
Als kind knooppunten worden toegevoegd aan de kinderen van de root-node, hun std::weak_ptr
lid parent
wordt ingesteld naar de root node. De parent
lid wordt gedeclareerd als een zwakke aanwijzer in tegenstelling tot een gedeelde aanwijzer zodat de referentietelling van de root node niet wordt verhoogd. Wanneer het root-knooppunt wordt gereset aan het einde van main()
, wordt de root vernietigd. Aangezien de enige overgebleven std::shared_ptr
verwijzingen naar de onderliggende knooppunten waren opgenomen in de root collectie children
, worden alle onderliggende knooppunten daarna vernietigd ook.
Vanwege implementatiedetails van het besturingsblok, wordt het toegewezen shared_ptr-geheugen mogelijk pas vrijgegeven als de shared_ptr
referentieteller en de weak_ptr
referentieteller beide nul bereiken.
#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)
}
Omdat std::weak_ptr
het object std::weak_ptr
wordt std::weak_ptr
niet levend houdt, is directe gegevenstoegang via een std::weak_ptr
niet mogelijk. In plaats daarvan biedt het een std::shared_ptr
lock()
die probeert een std::shared_ptr
op te halen naar het object std::shared_ptr
wordt verwezen:
#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());
}
}
Uniek eigendom (std :: unique_ptr)
Een std::unique_ptr
is een klassensjabloon die de levensduur van een dynamisch opgeslagen object beheert. In tegenstelling tot std::shared_ptr
, is het dynamische object op elk moment eigendom van slechts één instantie van een 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);
(Opmerking: std::unique_ptr
is beschikbaar sinds C ++ 11 en std::make_unique
sinds C ++ 14.)
Alleen de variabele ptr
bevat een pointer naar een dynamisch toegewezen int
. Wanneer een unieke aanwijzer die eigenaar is van een object buiten bereik valt, wordt het object in eigendom verwijderd, dat wil zeggen dat de destructor ervan wordt aangeroepen als het object van het klasse-type is en het geheugen voor dat object wordt vrijgegeven.
Om std::unique_ptr
en std::make_unique
met array-types, gebruikt u hun array-specialisaties:
// 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);
Je hebt toegang tot std::unique_ptr
net als een onbewerkte pointer, omdat het die operators overbelast.
U kunt het eigendom van de inhoud van een slimme aanwijzer overdragen naar een andere aanwijzer door std::move
, waardoor de oorspronkelijke slimme aanwijzer naar 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
aan functies als parameter:
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
van functies unique_ptr
. Dit is de geprefereerde C ++ 11-manier om fabrieksfuncties te schrijven, omdat het duidelijk de eigendomsemantiek van het rendement overbrengt: de beller is eigenaar van de resulterende unique_ptr
en is hiervoor verantwoordelijk.
std::unique_ptr<int> foo()
{
std::unique_ptr<int> ptr = std::make_unique<int>(59);
return ptr;
}
std::unique_ptr<int> ptr = foo();
Vergelijk dit met:
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.
De klassensjabloon make_unique
wordt geleverd sinds C ++ 14. Het is gemakkelijk om het handmatig toe te voegen aan C ++ 11 code:
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]()); }
In tegenstelling tot de domme slimme aanwijzer ( std::auto_ptr
), kan unique_ptr
ook worden geïnstantieerd met unique_ptr
( niet std::vector
). Eerdere voorbeelden waren voor scalaire toewijzingen. Als u bijvoorbeeld een dynamisch toegewezen geheel getal voor 10 elementen wilt hebben, geeft u int[]
als het sjabloontype (en niet alleen int
):
std::unique_ptr<int[]> arr_ptr = std::make_unique<int[]>(10);
Die kan worden vereenvoudigd met:
auto arr_ptr = std::make_unique<int[]>(10);
Nu gebruik je arr_ptr
alsof het een array is:
arr_ptr[2] = 10; // Modify third element
U hoeft zich geen zorgen te maken over de-allocatie. Deze gespecialiseerde sjabloonversie roept constructors en destructors op de juiste manier aan. Het gebruik van een vector
van unique_ptr
of een vector
zelf - is een persoonlijke keuze.
In eerdere versies dan C ++ 11 was std::auto_ptr
beschikbaar. In tegenstelling tot unique_ptr
is het toegestaan auto_ptr
s te kopiëren, waarna de bron ptr
het eigendom van de ingesloten aanwijzer verliest en het doel deze ontvangt.
Aangepaste deleters gebruiken om een wrapper naar een C-interface te maken
Veel C-interfaces zoals SDL2 hebben hun eigen verwijderingsfuncties. Dit betekent dat u smart pointers niet rechtstreeks kunt gebruiken:
std::unique_ptr<SDL_Surface> a; // won't work, UNSAFE!
In plaats daarvan moet u uw eigen deleter definiëren. De voorbeelden hier gebruiken de structuur SDL_Surface
die moet worden vrijgegeven met behulp van de functie SDL_FreeSurface()
, maar ze moeten kunnen worden aangepast aan vele andere C-interfaces.
De deleter moet opvraagbaar zijn met een pointerargument en kan daarom bijvoorbeeld een eenvoudige functiepointer zijn:
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);
Elk ander opvraagbaar object werkt ook, bijvoorbeeld een klasse met een 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
Dit biedt u niet alleen veilig, zonder overhead (als u unique_ptr
) automatisch geheugenbeheer, u krijgt ook uitzonderingsveiligheid.
Merk op dat de deleter deel uitmaakt van het type voor unique_ptr
, en de implementatie kan de lege unique_ptr
gebruiken om elke verandering in grootte voor lege aangepaste deleters te voorkomen. Dus terwijl std::unique_ptr<SDL_Surface, SurfaceDeleter>
en std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)>
hetzelfde probleem op een vergelijkbare manier oplossen, is het vorige type nog steeds alleen de grootte van een pointer terwijl de laatstgenoemde type moet twee pointers bevatten: zowel de SDL_Surface*
als de functiepointer! Wanneer aangepaste vrije deleters voor de functie worden gebruikt, verdient het de voorkeur om de functie in een leeg type te verpakken.
In gevallen waar referentietelling belangrijk is, zou men een shared_ptr
kunnen gebruiken in plaats van een unique_ptr
. De shared_ptr
slaat altijd een deleter op, hierdoor wordt het type deleter gewist, wat handig kan zijn in API's. De nadelen van het gebruik van shared_ptr
opzichte van unique_ptr
zijn hogere geheugenkosten voor het opslaan van de deleter en prestatiekosten voor het handhaven van de referentietelling.
// 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);
Met template auto
kunnen we het nog eenvoudiger maken om onze aangepaste deleters in te pakken:
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>>;
Waarmee het bovenstaande voorbeeld eenvoudig is:
unique_ptr_deleter<SDL_Surface, SDL_FreeSurface> c(pointer);
Het doel van auto
is hier om alle gratis functies te verwerken, of ze nu void
terugkeren (bijvoorbeeld SDL_FreeSurface
) of niet (bijvoorbeeld fclose
).
Uniek eigendom zonder semantiek verplaatsen (auto_ptr)
OPMERKING: std::auto_ptr
is verouderd in C ++ 11 en wordt verwijderd in C ++ 17. Gebruik dit alleen als u gedwongen bent om C ++ 03 of eerder te gebruiken en bereid bent voorzichtig te zijn. Het wordt aanbevolen om te verplaatsen naar unique_ptr in combinatie met std::move
om het gedrag van std::auto_ptr
te vervangen.
Voordat we std::unique_ptr
, voordat we semantiek hadden verplaatst, hadden we std::auto_ptr
. std::auto_ptr
biedt uniek eigendom, maar draagt het eigendom over na kopiëren.
Zoals bij alle slimme aanwijzers ruimt std::auto_ptr
automatisch bronnen op (zie RAII ):
{
std::auto_ptr<int> p(new int(42));
std::cout << *p;
} // p is deleted here, no memory leaked
maar staat slechts één eigenaar toe:
std::auto_ptr<X> px = ...;
std::auto_ptr<X> py = px;
// px is now empty
Dit maakt het mogelijk om std :: auto_ptr te gebruiken om eigendom expliciet en uniek te houden met het gevaar dat het eigendom onbedoeld verloren gaat:
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
De eigendomsoverdracht vond plaats in de "kopie" constructor. de copy constructor van de auto_ptr
en de operator voor de toewijzing van kopieën nemen hun operanden over naar een niet- const
referentie zodat ze kunnen worden gewijzigd. Een voorbeeldimplementatie kan zijn:
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 ... */
};
Hiermee wordt de semantiek van het kopiëren verbroken, waarbij voor het kopiëren van een object twee equivalente versies ervan vereist zijn. Voor elk kopieerbaar type, T
, zou ik moeten kunnen schrijven:
T a = ...;
T b(a);
assert(b == a);
Maar voor auto_ptr
is dit niet het geval. Daarom is het niet veilig om auto_ptr
s in containers te plaatsen.
Een shared_ptr krijgen die hiernaar verwijst
enable_shared_from_this
kunt u een geldige krijgen shared_ptr
bijvoorbeeld om this
.
Door het afleiden van je klas uit de klasse sjabloon enable_shared_from_this
, erft u een methode shared_from_this
dat terugkeert shared_ptr
bijvoorbeeld om this
.
Merk op dat het object op de eerste plaats moet worden gemaakt als een shared_ptr
:
#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
Opmerking (2) u kunt enable_shared_from_this
binnen de constructor aanroepen.
#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();
...
}
Als u shared_from_this()
op een object dat geen eigendom is van een shared_ptr
, zoals een lokaal automatisch object of een globaal object, is het gedrag niet gedefinieerd. Sinds C ++ 17 gooit het in plaats daarvan std::bad_alloc
.
Het gebruik van shared_from_this()
van een constructor is gelijk aan het gebruik op een object dat geen eigendom is van een shared_ptr
, omdat de objecten in bezit zijn van de shared_ptr
nadat de constructor terugkeert.
Casting std :: shared_ptr pointers
Het is niet mogelijk om static_cast
, const_cast
, dynamic_cast
en reinterpret_cast
op std::shared_ptr
te gebruiken om een aanwijzer te delen die eigenaar is van de aanwijzer die als argument wordt doorgegeven. In plaats daarvan moeten de functies std::static_pointer_cast
, std::const_pointer_cast
, std::dynamic_pointer_cast
en std::reinterpret_pointer_cast
worden gebruikt:
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));
Merk op dat std::reinterpret_pointer_cast
niet beschikbaar is in C ++ 11 en C ++ 14, omdat het alleen werd voorgesteld door N3920 en in februari 2014 werd overgenomen in Library Fundamentals TS. Het kan echter als volgt worden geïmplementeerd:
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())); }
Een slimme aanwijzer schrijven: value_ptr
Een value_ptr
is een slimme aanwijzer die zich gedraagt als een waarde. Wanneer gekopieerd, kopieert het de inhoud. Wanneer het is gemaakt, wordt de inhoud ervan gemaakt.
// 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)...)};
}
Deze specifieke value_ptr is alleen leeg als u deze samenstelt met empty_ptr_t
of als u er vanaf gaat. Het onthult het feit dat het een unique_ptr
, dus explicit operator bool() const
werkt eraan. .get()
is gewijzigd om een verwijzing te retourneren (omdat deze bijna nooit leeg is) en .get_pointer()
retourneert in plaats daarvan een aanwijzer.
Deze slimme aanwijzer kan nuttig zijn voor pImpl
gevallen, waar we waarde-semantiek willen, maar we willen ook de inhoud van de pImpl
buiten het implementatiebestand pImpl
.
Met een niet-standaard Copier
kan het zelfs virtuele basisklassen verwerken die weten hoe exemplaren van hun afgeleide te produceren en deze in waardetypes te veranderen.