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

C ++ 11 C ++ 17

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.

C ++ 17

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)

C ++ 11

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.
C ++ 14

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]()); }
C ++ 11

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); 
C ++ 17

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)

C ++ 11

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.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow