Szukaj…


Składnia

  • 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, ...));

Uwagi

C ++ nie jest językiem zarządzanym przez pamięć. Dynamicznie alokowana pamięć (tj. Obiekty utworzone za pomocą new ) zostanie „wyciekła”, jeśli nie zostanie jawnie zwolniona (z delete ). Obowiązkiem programisty jest upewnienie się, że pamięć dynamicznie przydzielana jest zwolniona przed odrzuceniem ostatniego wskaźnika do tego obiektu.

Za pomocą inteligentnych wskaźników można automatycznie zarządzać zakresem dynamicznie alokowanej pamięci (tzn. Gdy ostatnie odniesienie wskaźnika wykracza poza zakres, jest usuwane).

W większości przypadków inteligentne wskaźniki są lepsze niż wskaźniki „surowe”. Wyraźnie określają semantykę własności dynamicznie alokowanej pamięci, komunikując w swoich nazwach, czy obiekt ma być współużytkowany, czy niepowtarzalnie własnością.

Użyj #include <memory> aby móc korzystać ze inteligentnych wskaźników.

Współdzielenie własności (std :: shared_ptr)

Szablon klasy std::shared_ptr definiuje wspólny wskaźnik, który może współdzielić własność obiektu z innymi wspólnymi wskaźnikami. Kontrastuje to ze std::unique_ptr który reprezentuje wyłączną własność.

Współdzielenie jest realizowane za pomocą techniki znanej jako liczenie referencji, w której liczba wspólnych wskaźników wskazujących na obiekt jest przechowywana obok niego. Gdy liczba ta osiągnie zero, albo poprzez zniszczenie, albo ponowne przypisanie ostatniej instancji std::shared_ptr , obiekt jest automatycznie niszczony.


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

Aby utworzyć wiele inteligentnych wskaźników, które współużytkują ten sam obiekt, musimy utworzyć kolejny shared_ptr który aliuje pierwszy wspólny wskaźnik. Oto 2 sposoby na zrobienie tego:

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

Każdy z powyższych sposobów powoduje, że secondShared jest wspólnym wskaźnikiem, który dzieli własność naszej instancji Foo z firstShared .

Inteligentny wskaźnik działa jak surowy wskaźnik. Oznacza to, że możesz użyć * aby je wyrejestrować. Zwykły -> operator również działa:

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

Wreszcie, gdy ostatni aliasowany shared_ptr wykracza poza zakres, wywoływany jest destruktor naszej instancji Foo .

Ostrzeżenie: Skonstruowanie parametru shared_ptr może bad_alloc wyjątek bad_alloc gdy trzeba przydzielić dodatkowe dane dla semantyki własności dzielonej. Jeśli konstruktor zostanie przekazany zwykły wskaźnik, zakłada, że jest właścicielem wskazanego obiektu i wywołuje narzędzie usuwające, jeśli zostanie zgłoszony wyjątek. Oznacza to, że shared_ptr<T>(new T(args)) nie wycieknie z obiektu T jeśli alokacja shared_ptr<T> nie powiedzie się. Zaleca się jednak użycie make_shared<T>(args) lub allocate_shared<T>(alloc, args) , które umożliwiają implementację w celu zoptymalizowania alokacji pamięci.


Alokowanie tablic ([]) przy użyciu shared_ptr

C ++ 11 C ++ 17

Niestety nie ma bezpośredniego sposobu przydzielania tablic za pomocą make_shared<> .

Możliwe jest tworzenie tablic dla shared_ptr<> przy użyciu new i std::default_delete .

Na przykład, aby przydzielić tablicę 10 liczb całkowitych, możemy napisać kod jako

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

Określenie std::default_delete jest tutaj obowiązkowe, aby upewnić się, że przydzielona pamięć została poprawnie wyczyszczona przy użyciu delete[] .

Jeśli znamy rozmiar w czasie kompilacji, możemy to zrobić w ten sposób:

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>{}(); }

następnie make_shared_array<int[10]> zwraca shared_ptr<int> wskazując na 10 ints wszystkie zbudowane domyślnie.

C ++ 17

W C ++ 17 shared_ptr zyskał specjalne wsparcie dla typów tablic. To nie jest już konieczne, aby określić macierzach Deleter jawnie, a wspólny wskaźnik można dereferencjonowane pomocą [] operator indeksu tablicy:

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

Wskaźniki wspólne mogą wskazywać na pod-obiekt obiektu, którego jest właścicielem:

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

Zarówno p2 jak i p1 posiadają obiekt typu Foo , ale p2 wskazuje na jego element int x . Oznacza to, że jeśli p1 wyjdzie poza zakres lub zostanie ponownie przypisany, podstawowy obiekt Foo będzie nadal żywy, zapewniając, że p2 nie zwisnie.


Ważne: shared_ptr wie tylko o sobie i wszystkich innych shared_ptr które zostały utworzone za pomocą konstruktora aliasów. Nie wie o żadnych innych wskaźnikach, w tym o wszystkich innych shared_ptr utworzonych w odniesieniu do tej samej instancji 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!!

Przeniesienie własności shared_ptr

Domyślnie shared_ptr zwiększa liczbę referencji i nie przenosi własności. Można jednak dokonać przeniesienia własności za pomocą 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

Udostępnianie z tymczasową własnością (std :: poor_ptr)

Instancje std::weak_ptr mogą wskazywać na obiekty należące do instancji std::shared_ptr , stając się jedynie tymczasowymi właścicielami. Oznacza to, że słabe wskaźniki nie zmieniają liczby odniesień do obiektu, a zatem nie zapobiegają usunięciu obiektu, jeśli wszystkie wspólne wskaźniki obiektu zostaną ponownie przypisane lub zniszczone.


W poniższym przykładzie użyto instancji std::weak_ptr aby zniszczenie obiektu drzewa nie zostało zahamowane:

#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();
}

Gdy węzły potomne są dodawane do elementów potomnych węzła głównego, ich element parent element członkowski std::weak_ptr jest ustawiany na węzeł główny. Element parent elementu członkowskiego jest zadeklarowany jako słaby wskaźnik w przeciwieństwie do wskaźnika współdzielonego, dzięki czemu liczba referencji węzła głównego nie jest zwiększana. Gdy węzeł główny zostanie zresetowany na końcu main() , root zostanie zniszczony. Ponieważ jedyne pozostałe odwołania std::shared_ptr do węzłów potomnych były zawarte w elementach children kolekcji root, wszystkie węzły potomne zostały następnie zniszczone.

Ze względu na szczegóły implementacji bloku kontrolnego, pamięć przydzielona shared_ptr może nie zostać zwolniona, dopóki licznik referencyjny shared_ptr i licznik referencyjny weak_ptr osiągną 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)
}

Ponieważ std::weak_ptr nie utrzymuje std::weak_ptr obiektu przy życiu, bezpośredni dostęp do danych przez std::weak_ptr nie jest możliwy. Zamiast tego udostępnia funkcję członka lock() która próbuje pobrać std::shared_ptr do obiektu, do którego się odwołuje:

#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());
     }
}

Unikalna własność (std :: unique_ptr)

C ++ 11

std::unique_ptr to szablon klasy, który zarządza żywotnością dynamicznie przechowywanego obiektu. W przeciwieństwie do std::shared_ptr , obiekt dynamiczny jest własnością tylko jednej instancji std::unique_ptr w dowolnym momencie,


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

(Uwaga: std::unique_ptr jest dostępny od C ++ 11, a std::make_unique od C ++ 14).

Tylko zmienna ptr zawiera wskaźnik do dynamicznie przydzielanej int . Kiedy unikalny wskaźnik, który jest właścicielem obiektu, wykracza poza zakres, posiadany obiekt jest usuwany, tzn. Wywoływany jest jego destruktor, jeśli obiekt jest typu klasy, a pamięć tego obiektu zostaje zwolniona.

Aby używać std::unique_ptr i std::make_unique z std::make_unique tablic, użyj ich specjalizacji tablicowych:

// 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);

Możesz uzyskać dostęp do std::unique_ptr tak jak surowy wskaźnik, ponieważ przeciąża on te operatory.


Możesz przenieść własność zawartości inteligentnego wskaźnika na inny wskaźnik, używając std::move , co spowoduje, że oryginalny inteligentny wskaźnik wskaże 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)

Przekazywanie unique_ptr parametru do funkcji jako parametru:

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))

Zwracanie unique_ptr z funkcji. Jest to preferowany sposób pisania funkcji fabrycznych w C ++ 11, ponieważ wyraźnie przekazuje semantykę własności powrotu: wywołujący jest właścicielem wynikowego unique_ptr i jest za niego odpowiedzialny.

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

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

Porównaj to z:

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

Szablon klasy make_unique jest dostarczany od C ++ 14. Łatwo jest dodać go ręcznie do kodu 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]()); }
C ++ 11

W przeciwieństwie do głupiego inteligentnego wskaźnika ( std::auto_ptr ), unique_ptr może być również utworzony z alokacją wektorową ( nie std::vector ). Wcześniejsze przykłady dotyczyły przydziałów skalarnych . Na przykład, aby mieć dynamicznie przydzielaną tablicę liczb całkowitych dla 10 elementów, określ typ int[] jako typ szablonu (a nie tylko int ):

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

Co można uprościć:

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

Teraz używasz arr_ptr tak, jakby to była tablica:

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

Nie musisz się martwić o alokację. Ta specjalna wersja szablonu wywołuje odpowiednio konstruktory i destruktory. Korzystanie z wersji wektorowej unique_ptr lub samego vector - to osobisty wybór.

W wersjach wcześniejszych niż C ++ 11 dostępna była std::auto_ptr . W przeciwieństwie do unique_ptr dozwolone jest kopiowanie auto_ptr , na którym źródłowy ptr straci własność zawartego wskaźnika, a cel go odbierze.

Używanie niestandardowych programów do usuwania opakowania interfejsu C.

Wiele interfejsów C, takich jak SDL2, ma własne funkcje usuwania. Oznacza to, że nie można bezpośrednio używać inteligentnych wskaźników:

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

Zamiast tego musisz zdefiniować własny usuwacz. Przykłady tu użyć SDL_Surface strukturę, powinno być zwolnione przez ten SDL_FreeSurface() funkcji, ale powinny być dostosowane do wielu innych interfejsów C.

Separator musi być możliwy do wywołania za pomocą argumentu wskaźnika, a zatem może być np. Prostym wskaźnikiem funkcji:

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

Działa również każdy inny obiekt na żądanie, na przykład klasa z 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

Zapewnia to nie tylko bezpieczne, zerowe obciążenie (jeśli używasz unique_ptr ) automatycznego zarządzania pamięcią, ale także bezpieczeństwo wyjątkowe.

Należy pamiętać, że element usuwający jest częścią typu unique_ptr , a implementacja może użyć optymalizacji pustej podstawy, aby uniknąć zmiany rozmiaru pustych niestandardowych elementów usuwających. Tak więc, podczas gdy std::unique_ptr<SDL_Surface, SurfaceDeleter> i std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> rozwiązują ten sam problem w podobny sposób, poprzedni typ wciąż ma rozmiar wskaźnika, podczas gdy ten ostatni typ musi zawierać dwa wskaźniki: zarówno SDL_Surface* i wskaźnik funkcji! W przypadku niestandardowych programów usuwających funkcje najlepiej jest zawinąć funkcję w pusty typ.

W przypadkach, w których liczenie referencji jest ważne, można użyć parametru shared_ptr zamiast unique_ptr . shared_ptr zawsze przechowuje Deleter, to kasuje typ Deleter, co może być przydatne w API. Wady korzystania z shared_ptr porównaniu z unique_ptr obejmują wyższy koszt pamięci do przechowywania detektora i koszt wydajności do utrzymania liczby referencyjnej.

// 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

Dzięki template auto możemy jeszcze łatwiej owinąć nasze niestandardowe programy usuwające:

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>>;

Za pomocą którego powyższy przykład jest po prostu:

unique_ptr_deleter<SDL_Surface, SDL_FreeSurface> c(pointer);

Tutaj celem auto jest obsługa wszystkich darmowych funkcji, niezależnie od tego, czy zwracają void (np. SDL_FreeSurface ), czy nie (np. fclose ).

Unikalna własność bez semantyki ruchu (auto_ptr)

C ++ 11

UWAGA: std::auto_ptr jest przestarzałe w C ++ 11 i zostanie usunięte w C ++ 17. Powinieneś używać tego tylko wtedy, gdy jesteś zmuszony używać C ++ 03 lub wcześniejszej wersji i chcesz zachować ostrożność. Zaleca się przejście do unikalnej_ptr w połączeniu ze std::move aby zastąpić zachowanie std::auto_ptr .

Zanim mieliśmy std::unique_ptr , zanim mieliśmy semantykę przenoszenia, mieliśmy std::auto_ptr . std::auto_ptr zapewnia unikalną własność, ale przenosi własność po skopiowaniu.

Podobnie jak w przypadku wszystkich inteligentnych wskaźników, std::auto_ptr automatycznie czyści zasoby (patrz RAII ):

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

ale pozwala tylko jednemu właścicielowi:

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

Pozwala to na użycie std :: auto_ptr, aby zachować własność jawną i unikalną na wypadek niezamierzonej utraty własności:

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

Przeniesienie własności nastąpiło w konstruktorze „copy”. Konstruktor kopiujący auto_ptr i operator przypisania kopii pobierają swoje operandy przez odwołanie nie będące const , aby można je było modyfikować. Przykładową implementacją może być:

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 ... */
};

To łamie semantykę kopiowania, która wymaga, aby kopiowanie obiektu pozostawiło dwie równoważne wersje. Dla każdego typu kopii, T , powinienem być w stanie napisać:

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

Ale w przypadku auto_ptr tak nie jest. W związku z tym nie jest bezpieczne umieszczanie auto_ptr s w kontenerach.

Uzyskiwanie shared_ptr odnoszące się do tego

enable_shared_from_this pozwala uzyskać poprawną shared_ptr instancję do this .

Wyprowadzając swoją klasę z klasy szablonu enable_shared_from_this , dziedziczyć metoda A shared_from_this która zwraca shared_ptr instancję do this .

Zauważ, że obiekt musi zostać utworzony jako shared_ptr na pierwszym miejscu:

#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

Uwaga (2) nie można wywołać enable_shared_from_this wewnątrz konstruktora.

#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();
    ...
}

Jeśli użyjesz shared_from_this() na obiekcie, który nie jest własnością shared_ptr , takim jak lokalny automatyczny obiekt lub obiekt globalny, zachowanie jest niezdefiniowane. Od C ++ 17 rzuca zamiast tego std::bad_alloc .

Użycie shared_from_this() z konstruktora jest równoważne z użyciem go na obiekcie, który nie jest własnością shared_ptr , ponieważ obiekty są w posiadaniu shared_ptr po powrocie konstruktora.

Przesyłanie wskaźników std :: shared_ptr

Nie jest możliwe bezpośrednie użycie static_cast , const_cast , dynamic_cast i reinterpret_cast na std::shared_ptr aby pobrać własność wskaźnika dzielącą się ze wskaźnikiem przekazywanym jako argument. Zamiast tego należy std::static_pointer_cast funkcji std::static_pointer_cast , std::const_pointer_cast , std::dynamic_pointer_cast i 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));

Zauważ, że std::reinterpret_pointer_cast nie jest dostępny w C ++ 11 i C ++ 14, ponieważ został zaproponowany tylko przez N3920 i przyjęty do Library Fundamentals TS w lutym 2014 r . Można go jednak zaimplementować w następujący sposób:

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())); }

Pisanie inteligentnego wskaźnika: wartość_ptr

value_ptr to inteligentny wskaźnik, który zachowuje się jak wartość. Po skopiowaniu kopiuje zawartość. Po utworzeniu tworzy zawartość.

// 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)...)};
}

Ten konkretny value_ptr jest pusty tylko wtedy, gdy empty_ptr_t go z empty_ptr_t lub jeśli z niego przejdziesz. Ujawnia fakt, że jest to unique_ptr , więc działa na nim explicit operator bool() const . .get() zostało zmienione, aby .get_pointer() odwołanie (ponieważ prawie nigdy nie jest puste), a .get_pointer() zwraca wskaźnik zamiast tego.

Ten inteligentny wskaźnik może być przydatny w przypadkach pImpl , w których chcemy semantyki wartości, ale nie chcemy również ujawniać zawartości pImpl poza plikiem implementacyjnym.

Za pomocą domyślnej Copier może nawet obsługiwać wirtualne klasy podstawowe, które potrafią tworzyć instancje ich pochodnych i przekształcać je w typy wartości.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow