Jak przekazać unikalny argument ptr konstruktorowi lub funkcji?

Jestem nowy, aby przenieść semantykę w C++11 i nie wiem zbyt dobrze, jak obsługiwać unique_ptr parametry w konstruktorach lub funkcjach. Rozważ, że ta klasa odwołuje się do siebie:

#include <memory>

class Base
{
  public:

    typedef unique_ptr<Base> UPtr;

    Base(){}
    Base(Base::UPtr n):next(std::move(n)){}

    virtual ~Base(){}

    void setNext(Base::UPtr n)
    {
      next = std::move(n);
    }

  protected :

    Base::UPtr next;

};

Czy tak powinienem pisać funkcje przyjmujące unique_ptr argumenty?

I Czy muszę używać std::move w kodzie wywołującym?

Base::UPtr b1;
Base::UPtr b2(new Base());

b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?
Author: R. Martinho Fernandes, 2011-11-13

6 answers

Oto możliwe sposoby na wzięcie unikalnego wskaźnika jako argumentu, a także związane z nim znaczenie.

(A) Według Wartości

Base(std::unique_ptr<Base> n)
  : next(std::move(n)) {}
Aby użytkownik mógł to wywołać, musi wykonać jedną z następujących czynności:]}
Base newBase(std::move(nextBase));
Base fromTemp(std::unique_ptr<Base>(new Base(...));

Pobranie unikalnego wskaźnika przez wartość oznacza, że przekazujesz własność wskaźnika do danej funkcji/obiektu/etc. Po zbudowaniu newBase, nextBase jest gwarantowane, że będzie puste. Nie posiadasz przedmiotu i już nawet nie masz na to wskaznika. Zniknął.

Jest to zapewnione, ponieważ bierzemy parametr przez wartość. std::move właściwie } nie porusza } niczego; to tylko fantazyjna Obsada. std::move(nextBase) zwraca Base&&, która jest odniesieniem do wartości r nextBase. To wszystko.

Ponieważ Base::Base(std::unique_ptr<Base> n) pobiera swój argument przez wartość, a nie przez odniesienie do wartości r, C++ automatycznie zbuduje dla nas tymczasowy. Tworzy std::unique_ptr<Base> z Base&&, którą daliśmy funkcji poprzez std::move(nextBase). Jest to konstrukcja tej tymczasowej, która w rzeczywistości przenosi wartość z nextBase do argumentu funkcji n.

(B) przez odniesienie non-const l-value

Base(std::unique_ptr<Base> &n)
  : next(std::move(n)) {}

To musi być wywołane NA rzeczywistej wartości l (nazwanej zmiennej). Nie można go nazwać tymczasowym, jak to:

Base newBase(std::unique_ptr<Base>(new Base)); //Illegal in this case.

Znaczenie tego jest takie samo jak znaczenie każdego innego użycia odwołań nie-const: funkcja może lub nie może rościć sobie prawo własności wskaźnika. Podany kod:

Base newBase(nextBase);

Nie ma gwarancji, że nextBase jest pusty. To może być puste; może nie. To naprawdę zależy od tego, co Base::Base(std::unique_ptr<Base> &n) chce zrobić. Z tego powodu nie jest bardzo widoczne tylko z podpisu funkcji, co się stanie; musisz przeczytać implementację (lub powiązaną dokumentację).

Z tego powodu nie sugerowałbym tego jako interfejsu.

(C) przez const l-wartość odniesienia

Base(std::unique_ptr<Base> const &n);

Nie pokazuję implementacji, ponieważ nie możesz się ruszyć z. Przekazując const&, mówisz, że funkcja może uzyskać dostęp do Base za pomocą wskaźnika, ale nie może przechowywać w dowolnym miejscu. Nie może rościć sobie prawa własności.

To może być przydatne. Niekoniecznie w twoim konkretnym przypadku, ale zawsze dobrze jest być w stanie przekazać komuś wskaźnik i wiedzieć, że nie może (bez łamania zasad C++, jak bez odrzucania {28]}) rościć sobie prawa własności do niego. Nie mogą go przechowywać. Mogą przekazać je innym, ale ci inni muszą przestrzegać tych samych zasad.

(D) przez odniesienie do wartości r

Base(std::unique_ptr<Base> &&n)
  : next(std::move(n)) {}

Jest to mniej więcej identyczne z przypadkiem "by non-const l-value reference". Różnice to dwie rzeczy.

  1. You can pass a temporary:

    Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
    
  2. Musisz używać std::move podczas przekazywania Nie-tymczasowych argumentów.

To drugie jest naprawdę problemem. Jeśli widzisz to linia:
Base newBase(std::move(nextBase));

Masz uzasadnione oczekiwania, że po zakończeniu tej linii nextBase powinna być pusta. Trzeba było go przenieść. W końcu siedzicie tam i mówicie, że nastąpił ruch.

Problem polega na tym, że nie został. nie jest to zagwarantowane, aby zostały przeniesione z. To może zostały przeniesione z, ale dowiesz się tylko patrząc na kod źródłowy. Nie można stwierdzić tylko z funkcji podpis.

Zalecenia

  • (A) według wartości: jeśli masz na myśli, że dla funkcji do twierdzenia własność Z unique_ptr, weź ją według wartości.
  • (C) przez const l-value reference: jeśli masz na myśli, aby funkcja po prostu używała unique_ptr przez czas wykonywania tej funkcji, Weź to przez const&. Alternatywnie, przekazać & lub const& do wskazanego typu rzeczywistego, zamiast używać unique_ptr.
  • (D) przez wartość r reference: jeśli funkcja może lub nie może rościć sobie prawa własności (w zależności od wewnętrznych ścieżek kodu), weź ją przez &&. Ale zdecydowanie odradzam robienie tego w miarę możliwości.

Jak manipulować unique_ptr

Nie można skopiować unique_ptr. Możesz go tylko przenieść. Poprawnym sposobem jest użycie standardowej funkcji biblioteki std::move.

Jeśli weźmiesz unique_ptr według wartości, możesz się z niej swobodnie poruszać. Ale ruch nie dzieje się z powodu std::move. Weź następujące stwierdzenie:

std::unique_ptr<Base> newPtr(std::move(oldPtr));

To naprawdę dwa stwierdzenia:

std::unique_ptr<Base> &&temporary = std::move(oldPtr);
std::unique_ptr<Base> newPtr(temporary);

(Uwaga: powyższy kod nie kompiluje się technicznie, ponieważ nie tymczasowe odniesienia do wartości r nie są w rzeczywistości wartościami r. Jest tu tylko w celach demonstracyjnych).

temporary jest tylko odniesieniem do wartości r oldPtr. Jest w konstruktorze Z newPtr gdzie następuje ruch. unique_ptr ' s move constructor (konstruktor, który bierze && do siebie) jest tym, co robi rzeczywisty ruch.

Jeśli masz wartość unique_ptr i chcesz ją gdzieś przechowywać, musisz użyć do przechowywania.

 705
Author: Nicol Bolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-03 12:01:19

Spróbujmy podać różne realne tryby przekazywania wskaźników do obiektów, których pamięć jest zarządzana przez instancję szablonu klasy std::unique_ptr; odnosi się to również do starszego szablonu klasy std::auto_ptr (który, jak sądzę, pozwala na wszystkie zastosowania tego unikalnego wskaźnika, ale dla którego dodatkowo modyfikowalne wartości LV będą akceptowane tam, gdzie oczekuje się wartości R, bez konieczności wywoływania std::move), a w pewnym stopniu także do std::shared_ptr.

Jako konkretny przykład do dyskusji będę rozważmy następujący prosty typ listy

struct node;
typedef std::unique_ptr<node> list;
struct node { int entry; list next; }

Instancje takiej listy (które nie mogą współdzielić części z innymi instancjami lub być okrągłe) są w całości własnością tego, kto posiada początkowy wskaźnik list. Jeśli kod klienta wie, że lista, którą przechowuje, nigdy nie będzie pusta, może również wybrać przechowywanie pierwszej node bezpośrednio, a nie list. Nie trzeba definiować destruktora dla node: ponieważ destruktory dla jej pól są wywoływane automatycznie, cała lista będzie być usuwane rekurencyjnie przez inteligentny Destruktor wskaźnika po zakończeniu życia wskaźnika lub węzła.

Ten typ rekurencyjny daje okazję do omówienia niektórych przypadków, które są mniej widoczne w przypadku inteligentnego wskaźnika do zwykłych danych. Również same funkcje czasami dostarczają (rekurencyjnie) przykład kodu klienta. Typedef dla list jest oczywiście tendencyjny w kierunku unique_ptr, ale definicję można zmienić na auto_ptr lub shared_ptr zamiast tego bez większej potrzeby zmiana na to, co zostało powiedziane poniżej (zwłaszcza w odniesieniu do zapewnienia bezpieczeństwa WYJĄTKÓW bez konieczności pisania destruktorów).

Tryby przekazywania inteligentnych wskaźników wokół

Tryb 0: podanie wskaźnika lub argumentu referencyjnego zamiast inteligentnego wskaźnika

Jeśli twoja funkcja nie dotyczy własności, jest to preferowana metoda: nie zmuszaj jej do użycia inteligentnego wskaźnika. W tym przypadku twoja funkcja nie musi się martwić kto jest właścicielem wskazywanego obiektu, lub w jaki sposób zarządzana jest własność, więc przekazywanie surowego wskaźnika jest zarówno całkowicie bezpieczne, jak i najbardziej elastyczne, ponieważ niezależnie od własności klient może zawsze wytworzyć surowy wskaźnik (poprzez wywołanie metody get lub z adresu operatora &).

Na przykład funkcja do obliczania długości takiej listy, nie powinna być argumentem list, ale surowym wskaźnikiem:

size_t length(const node* p)
{ size_t l=0; for ( ; p!=nullptr; p=p->next.get()) ++l; return l; }

Klient, który posiada zmienną list head może wywołać tę funkcję jako length(head.get()), podczas gdy klient, który wybrał zamiast przechowywania node n reprezentującą niepustą listę, może wywołać length(&n).

Jeśli wskaźnik jest gwarantowany, że nie jest null (co nie ma miejsca w tym przypadku, ponieważ listy mogą być puste), można wolą przekazać referencję, a nie wskaźnik. Może to być wskaźnik / odniesienie do nie - const, jeśli funkcja musi zaktualizować zawartość węzła(węzłów), bez dodawania lub usuwania żadnego z nich(to ostatnie wymagałoby posiadania).

Ciekawy przypadek to, co należy do kategorii mode 0, to tworzenie (głębokiej) kopii listy; podczas gdy funkcja wykonująca to zadanie musi oczywiście przenieść własność tworzonej przez siebie kopii, Nie dotyczy to własności listy, którą kopiuje. Można go więc zdefiniować następująco:

list copy(const node* p)
{ return list( p==nullptr ? nullptr : new node{p->entry,copy(p->next.get())} ); }

Ten kod zasługuje na dokładne przyjrzenie się, zarówno dla pytania, dlaczego w ogóle kompiluje (wynik rekurencyjnego wywołania copy na liście inicjalizatorów wiąże się z argumentem referencyjnym rvalue w konstruktorze move unique_ptr<node>, Alias list, podczas inicjalizacji pola next wygenerowanego node), a także dla pytania, dlaczego jest to bezpieczne dla wyjątków (jeśli podczas rekurencyjnego procesu alokacji zabraknie pamięci i jakieś wywołanie new rzuca std::bad_alloc, wtedy w tym czasie wskaźnik do częściowo zbudowanej listy jest przechowywany anonimowo w tymczasowym typie list utworzonym dla listy inicjalizacyjnej, a jego Destruktor oczyści lista częściowa). Swoją drogą trzeba oprzeć się pokusie zastąpienia (jak I nie można skonstruować inteligentnego wskaźnika z (surowego) wskaźnika do stałej , nawet jeśli wiadomo, że jest null.

Tryb 1: podanie inteligentnego wskaźnika przez wartość

Funkcja, która pobiera wartość inteligentnego wskaźnika jako argument, od razu przejmuje wskazywany obiekt: inteligentny wskaźnik, który przechowuje wywołujący (czy to w nazwanej zmiennej, czy anonimowej tymczasowej), jest kopiowany do wartości argumentu przy wejściu funkcji i wskaźnik wywołującego stał się null(W przypadku tymczasowej Kopia mogła zostać usunięta, ale w każdym przypadku wywołujący utracił dostęp do wskazywanego obiektu). Chciałbym zadzwonić w tym trybie zadzwoń gotówką: dzwoniący płaci z góry za wywołaną usługę i nie może mieć złudzeń co do własności po wywołaniu. Aby to wyjaśnić, reguły języka wymagają, aby wywołujący zawijał argument w std::move, jeśli inteligentny wskaźnik jest trzymany w zmiennej (technicznie, jeśli argument jest lvalue); w tym przypadku (ale nie dla trybu 3 poniżej) ta funkcja robi to, co sugeruje jej nazwa, a mianowicie przenosi wartość ze zmiennej do tymczasowej, pozostawiając zmienną null.

W przypadkach, gdy wywołana funkcja bezwarunkowo przejmuje własność (pilfers) wskazywanego obiektu, ten tryb używany z std::unique_ptr lub std::auto_ptr jest dobrym sposobem przekazania wskaźnika wraz z jego własnością, co pozwala uniknąć ryzyka wycieku pamięci. / Align = "Left" / pomyśl, że jest tylko kilka sytuacji, w których tryb 3 poniżej nie jest preferowany (choć trochę) nad trybem 1. Z tego powodu nie podam żadnych przykładów użycia tego trybu. (Ale zobacz reversed przykład trybu 3 poniżej, gdzie zauważono, że tryb 1 zrobiłby co najmniej równie dobrze.) Jeśli funkcja pobiera więcej argumentów niż tylko ten wskaźnik, może się zdarzyć, że istnieje dodatkowo techniczny powód, aby uniknąć trybu 1 (z std::unique_ptr lub std::auto_ptr): ponieważ rzeczywista operacja move ma miejsce podczas przekazywania zmiennej wskaźnikowej {[37] } przez wyrażenie std::move(p), nie można założyć, że p posiada użyteczną wartość podczas oceniania innych argumentów (kolejność oceny jest nieokreślona), co może prowadzić do subtelnych błędów; dla kontrastu, użycie trybu 3 zapewnia, że żaden ruch z p Nie odbywa się przed wywołaniem funkcji, więc inne argumenty mogą bezpiecznie uzyskać dostęp do wartości przez p.

Gdy jest używany z std::shared_ptr, ten tryb jest interesujący w tym, że z pojedynczym definicja funkcji pozwala ona wybrać , czy zachować kopię współdzielenia wskaźnika dla siebie podczas tworzenia nowej kopii współdzielenia, która ma być używana przez funkcję (dzieje się tak, gdy podany jest argument lvalue; Konstruktor kopiujący współdzielonych wskaźników używany podczas wywołania zwiększa liczbę referencji), czy po prostu dać funkcji kopię wskaźnika bez zatrzymywania jednego lub dotykania liczby referencji (dzieje się tak, gdy podany jest argument rvalue, ewentualnie lvalue zawinięty w wywołanie std::move). Na przykład

void f(std::shared_ptr<X> x) // call by shared cash
{ container.insert(std::move(x)); } // store shared pointer in container

void client()
{ std::shared_ptr<X> p = std::make_shared<X>(args);
  f(p); // lvalue argument; store pointer in container but keep a copy
  f(std::make_shared<X>(args)); // prvalue argument; fresh pointer is just stored away
  f(std::move(p)); // xvalue argument; p is transferred to container and left null
}

To samo można osiągnąć poprzez oddzielne zdefiniowanie void f(const std::shared_ptr<X>& x) (Dla przypadku lvalue) i void f(std::shared_ptr<X>&& x) (dla przypadku rvalue), z ciałami funkcyjnymi różniącymi się tylko tym, że pierwsza wersja wywołuje semantykę kopiowania (używając konstrukcji kopiowania/przypisania przy użyciu x), ale druga wersja przenosi semantykę (pisząc std::move(x) zamiast, jak w przykładzie kodu). Tak więc w przypadku współdzielonych wskaźników tryb 1 może być przydatny, aby uniknąć kodu duplikacja.

Tryb 2: przekazywanie inteligentnego wskaźnika przez (modyfikowalne) lvalue reference

Tutaj funkcja wymaga tylko modyfikacji odniesienia do inteligentnego wskaźnika, ale nie wskazuje, co z nim zrobi. Chciałbym zadzwonić tą metodą zadzwoń kartą: dzwoniący zapewnia płatność podając numer karty kredytowej. Odniesienie może być użyte do przejęcia własności wskazywanego obiektu, ale nie musi. Tryb ten wymaga podania modyfikowalny argument lvalue, odpowiadający temu, że pożądanym efektem funkcji może być pozostawienie wartości użytecznej w zmiennej argumentu. Wywołujący z wyrażeniem rvalue, które chce przekazać do takiej funkcji, będzie zmuszony przechowywać je w nazwanej zmiennej, aby móc wykonać wywołanie, ponieważ Język zapewnia tylko domyślną konwersję do stałej referencji lvalue (odnoszącej się do tymczasowej) z wartości rvalue. (W przeciwieństwie do przeciwnej sytuacji obsługiwanej przez std::move, rzut z Y&& do Y&, Z Y typu inteligentnego wskaźnika, nie jest możliwe; niemniej jednak konwersja ta może być uzyskana za pomocą prostej funkcji szablonu, jeśli jest to naprawdę pożądane; zobacz https://stackoverflow.com/a/24868376/1436796 ). w przypadku, gdy wywołana funkcja zamierza bezwarunkowo przejąć własność obiektu, kradnąc od argumentu, obowiązek podania argumentu lvalue daje zły sygnał: zmienna nie będzie miała użytecznej wartości po sprawdzam. Dlatego tryb 3, który daje identyczne możliwości wewnątrz naszej funkcji, ale prosi wywołujących o podanie wartości R, powinien być preferowany do takiego użycia.

Istnieje jednak ważny przypadek użycia trybu 2, a mianowicie funkcje, które mogą modyfikować wskaźnik, lub obiekt wskazywany na w sposób, który wymaga własności. Na przykład funkcja, która prefiksuje węzeł do list, dostarcza przykład takiego użycia:

void prepend (int x, list& l) { l = list( new node{ x, std::move(l)} ); }

Najwyraźniej byłoby to niepożądane tutaj aby zmusić wywołujących do użycia std::move, ponieważ ich inteligentny wskaźnik nadal posiada dobrze zdefiniowaną i niepustą listę po wywołaniu, choć inną niż wcześniej.

Ponownie warto obserwować, co się stanie, jeśli wywołanie prepend zawiedzie z powodu braku wolnej pamięci. Następnie wywołanie new rzuci std::bad_alloc; w tym momencie, ponieważ nie można przydzielić node, jest pewne, że przekazane odniesienie rvalue (tryb 3) z std::move(l) nie może jeszcze zostać skradzione, ponieważ byłoby to zrobione do Utwórz pole next z pola node, które nie zostało przydzielone. Tak więc oryginalny inteligentny wskaźnik l nadal przechowuje oryginalną listę, gdy błąd zostanie wyrzucony; ta lista zostanie odpowiednio zniszczona przez inteligentny Destruktor wskaźnika, lub jeśli {68] } przetrwa dzięki wystarczająco wczesnej klauzuli {70]}, nadal będzie przechowywać oryginalną listę.

To był konstruktywny przykład; z przymrużeniem oka na to pytanie można też podać bardziej destrukcyjny przykład usunięcie pierwszego węzła zawierającego daną wartość, jeśli taka istnieje:

void remove_first(int x, list& l)
{ list* p = &l;
  while ((*p).get()!=nullptr and (*p)->entry!=x)
    p = &(*p)->next;
  if ((*p).get()!=nullptr)
    (*p).reset((*p)->next.release()); // or equivalent: *p = std::move((*p)->next); 
}

Znowu poprawność jest tu dość subtelna. W ostatnim poleceniu wskaźnik (*p)->next utrzymywany wewnątrz węzła do usunięcia jest niepowiązany (przez release, który zwraca wskaźnik, ale czyni oryginalny null) przed reset (domyślnie) niszczy ten węzeł (gdy niszczy starą wartość posiadaną przez p), zapewniając, że jeden i tylko jeden węzeł zostanie zniszczony w tym czasie. (W alternatywnym forma wspomniana w komentarzu, ten czas będzie pozostawiony do wewnętrznej implementacji operatora przypisania ruchu instancji std::unique_ptr list; standard mówi 20.7.1.2.3;2, że ten operator powinien działać " tak, jakby wywołując reset(u.release())", skąd czas powinien być bezpieczny również tutaj.)

Zauważ, że prepend i remove_first nie mogą być wywołane przez klientów, którzy przechowują lokalną zmienną node Dla zawsze niepustej listy, i słusznie, ponieważ podane implementacje nie mogą działać dla takich sprawy.

Tryb 3: przekazywanie inteligentnego wskaźnika przez (modyfikowalne) referencję rvalue

Jest to preferowany tryb, którego można używać po prostu przejmując własność wskaźnika. I would like to call this method call by check: caller musi zaakceptować zrzeczenie się własności, tak jakby dostarczenie gotówki, podpisując czek, ale rzeczywista wypłata jest odłożona do czasu, aż wywołana funkcja faktycznie dokona kradzieży wskaźnika (dokładnie tak, jak robi to przy użyciu trybu 2). "Podpisanie czeku" oznacza to, że wywołujący muszą zawinąć argument w std::move (jak w trybie 1), jeśli jest to lvalue (jeśli jest to rvalue, część "rezygnacja z własności" jest oczywista i nie wymaga oddzielnego kodu).

Zauważ, że technicznie tryb 3 zachowuje się dokładnie tak, jak tryb 2, więc wywołana funkcja nie musi przejmować własności; jednak nalegam, aby jeśli istnieje jakakolwiek niepewność co do przeniesienia własności (w normalnym użytkowaniu), tryb 2 powinien być preferowany od trybu 3, tak aby korzystanie z trybu 3 było w domyśle sygnał do wywołujących, że rezygnują z własności. Można powtórzyć, że tylko argument mode 1 przekazujący sygnały wymusiły utratę własności na rozmówcach. Jeśli jednak klient ma jakiekolwiek wątpliwości co do intencji wywołanej funkcji, powinien znać specyfikację wywoływanej funkcji, co powinno usunąć wszelkie wątpliwości.

Zaskakująco trudno jest znaleźć typowy przykład dotyczący naszego typu list, który używa przekazywania argumentów mode 3. Przeprowadzki a lista b na końcu innej listy a jest typowym przykładem; jednak a (która przetrwa i przechowuje wynik operacji) jest lepiej przekazywana w trybie 2:

void append (list& a, list&& b)
{ list* p=&a;
  while ((*p).get()!=nullptr) // find end of list a
    p=&(*p)->next;
  *p = std::move(b); // attach b; the variable b relinquishes ownership here
}

Czysty przykład przekazywania argumentów trybu 3 jest następujący, który pobiera listę (i jej własność) i zwraca listę zawierającą identyczne węzły w odwrotnej kolejności.

list reversed (list&& l) noexcept // pilfering reversal of list
{ list p(l.release()); // move list into temporary for traversal
  list result(nullptr);
  while (p.get()!=nullptr)
  { // permute: result --> p->next --> p --> (cycle to result)
    result.swap(p->next);
    result.swap(p);
  }
  return result;
}

Ta funkcja może być wywołana tak jak w l = reversed(std::move(l));, aby odwrócić listę do siebie, ale odwrócona lista może być również używana inaczej.

Tutaj argument jest natychmiast przenoszony do lokalnej zmiennej dla efektywności (można było użyć parametru l bezpośrednio w miejscu p, ale wtedy dostęp do niego za każdym razem wymagałby dodatkowego poziomu indrection); stąd różnica w przekazywaniu argumentów mode 1 jest minimalna. W rzeczywistości używając tego trybu, argument mógł służyć bezpośrednio jako zmienna lokalna, unikając w ten sposób początkowego ruchu; jest to tylko przykład ogólnej zasady, że jeśli argument przekazany przez referencję służy tylko do inicjalizacji zmiennej lokalnej, równie dobrze można przekazać ją przez wartość i użyć parametru jako zmiennej lokalnej.

Korzystanie z trybu 3 wydaje się być zalecane przez standard, o czym świadczy fakt, że wszystkie dostarczone funkcje biblioteczne, które przenoszą własność inteligentnych wskaźników za pomocą trybu 3. Szczególnie przekonującym przypadkiem jest konstruktor std::shared_ptr<T>(auto_ptr<T>&& p). Konstruktor ten użył (w std::tr1) do wzięcia modyfikowalnego lvalue referencji (podobnie jak Konstruktor kopiujący auto_ptr<T>&), a zatem może być wywołany z auto_ptr<T> lvalue p jak w std::shared_ptr<T> q(p), po czym p został zresetowany do null. Ze względu na zmianę trybu 2 na 3 w przekazywaniu argumentów, ten stary kod musi teraz zostać przepisany na std::shared_ptr<T> q(std::move(p)) i będzie dalej działał. Rozumiem, że Komitetowi nie spodobał się tryb 2, ale mieli możliwość zmiany na tryb 1, definiując zamiast tego std::shared_ptr<T>(auto_ptr<T> p), mogli zapewnić, że stary kod będzie działał bez modyfikacji, ponieważ (w przeciwieństwie do unique-pointers) Auto-pointery mogą być bezszelestnie dereferowane do wartości (sam obiekt pointer jest resetowany do null w procesie). Najwyraźniej Komitet tak bardzo wolał zalecać mode 3 zamiast mode 1, że wolał aktywnie łamać istniejący kod {116]} zamiast używać mode 1 nawet do już przestarzałego użycia.

Kiedy wybrać tryb 3 nad trybem 1

Mode 1 jest doskonale użyteczny w wielu przypadkach i może być preferowany niż mode 3 w przypadki, w których założenie własności przybrałoby formę przeniesienia inteligentnego wskaźnika do zmiennej lokalnej, jak w powyższym przykładzie reversed. Jednak widzę dwa powody, dla których preferuję tryb 3 w bardziej ogólnym przypadku: {]}

  • Nieco bardziej wydajne jest przekazywanie referencji niż tworzenie tymczasowego i nix starego wskaźnika (obsługa gotówki jest nieco pracochłonna); w niektórych scenariuszach wskaźnik może być przekazany kilka razy w niezmienionej postaci do innej funkcji, zanim zostanie faktycznie okradli. Takie przejście będzie generalnie wymagało napisania std::move (chyba że używany jest tryb 2), ale zauważ, że jest to tylko obsada, która tak naprawdę nic nie robi( w szczególności nie dereferuje), więc ma zerowy koszt.

  • Czy można sobie wyobrazić, że cokolwiek rzuca wyjątek między początkiem wywołania funkcji a punktem, w którym to (lub niektóre zawarte wywołanie) faktycznie przenosi obiekt wskazywany do innej struktury danych (a ten wyjątek nie jest jeszcze w trybie 1 obiekt, do którego odnosi się inteligentny wskaźnik, zostanie zniszczony zanim klauzula catch będzie mogła obsłużyć wyjątek (ponieważ parametr funkcji został zniszczony podczas rozwijania stosu), ale nie w trybie 3. To ostatnie daje wywołującemu możliwość odzyskania danych obiektu w takich przypadkach (poprzez wyłapanie wyjątku). Należy pamiętać, że tryb 1 tutaj nie powoduje wycieku pamięci , ale może prowadzić do nieodwracalnej utraty danych do programu, co również może być niepożądane.

Zwracanie inteligentnego wskaźnika: zawsze według wartości

Podsumowując słowo o zwracaniu inteligentnego wskaźnika, prawdopodobnie wskazującego na obiekt stworzony do użycia przez wywołującego. Nie jest to tak naprawdę przypadek porównywalny z przekazywaniem wskaźników do funkcji, ale dla kompletności chciałbym nalegać, aby w takich przypadkach zawsze zwracać przez wartość (i nie używać std::move w return oświadczenie). Nikt nie chce uzyskać odniesienia do wskaźnika, który prawdopodobnie właśnie został niklowany.

 44
Author: Marc van Leeuwen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:37

Tak, musisz, jeśli weźmiesz {[0] } przez wartość w konstruktorze. Explicity to miła rzecz. Ponieważ unique_ptr jest nie do skopiowania( private copy ctor), to co napisałeś powinno spowodować błąd kompilatora.

 4
Author: Xeo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-13 20:06:54

Edit: ta odpowiedź jest błędna, mimo że ściśle mówiąc kod działa. Zostawiam go tutaj tylko dlatego, że dyskusja pod nim jest zbyt przydatna. Ta druga odpowiedź jest najlepszą odpowiedzią udzieloną w czasie, gdy ostatnio edytowałem to: Jak przekazać argument unique_ptr do konstruktora lub funkcji?

Podstawową ideą ::std::move jest to, że ludzie, którzy przekazują ci unique_ptr powinni używać go do wyrażania wiedzy, że znają unique_ptr przekazują wolę stracić własność.

Oznacza to, że powinieneś używać w swoich metodach odniesienia rvalue do unique_ptr, a nie samego unique_ptr. To i tak nie zadziała, ponieważ Przejście W zwykłym starym unique_ptr wymagałoby zrobienia kopii, a to jest wyraźnie zabronione w interfejsie dla unique_ptr. Co ciekawe, użycie referencji o nazwie rvalue ponownie przekształca ją w lvalue, więc musisz użyć ::std::move wewnątrz Twoich metod.

Oznacza to, że Twoje dwie metody powinny wyglądać to:

Base(Base::UPtr &&n) : next(::std::move(n)) {} // Spaces for readability

void setNext(Base::UPtr &&n) { next = ::std::move(n); }

Wtedy ludzie używający metod zrobiliby to:

Base::UPtr objptr{ new Base; }
Base::UPtr objptr2{ new Base; }
Base fred(::std::move(objptr)); // objptr now loses ownership
fred.setNext(::std::move(objptr2)); // objptr2 now loses ownership

Jak widzisz, ::std::move wyraża, że wskaźnik straci własność w punkcie, w którym jest to najbardziej istotne i pomocne. Gdyby stało się to w sposób niewidoczny, byłoby bardzo mylące dla osób korzystających z twojej klasy, aby nagle utraciły własność bez wyraźnego powodu.

 2
Author: Omnifarious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:45
Base(Base::UPtr n):next(std::move(n)) {}

Powinno być dużo lepiej jak

Base(Base::UPtr&& n):next(std::forward<Base::UPtr>(n)) {}

I

void setNext(Base::UPtr n)

Powinno być

void setNext(Base::UPtr&& n)

Z tym samym ciałem.

I ... co to jest evt w handle()??

 0
Author: Emilio Garavaglia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-13 20:08:30

Do góry głosowana odpowiedź. Wolę przejść przez rvalue reference.

Rozumiem, jaki problem może powodować przechodzenie przez referencję rvalue. Ale Podzielmy ten problem na dwie strony:

  • dla rozmówcy:

Muszę napisać kod Base newBase(std::move(<lvalue>)) LUB Base newBase(<rvalue>).

  • dla callee:

Autor Biblioteki powinien zagwarantować, że rzeczywiście przeniesie unique_ptr do elementu inicjalizującego, jeśli chce posiadać własność.

To wszystko.

Jeśli przejdziesz obok rvalue reference, wywoła tylko jedną instrukcję" move", ale jeśli zostanie przekazana przez wartość, będzie to dwie.

Tak, jeśli autor biblioteki nie jest ekspertem w tym zakresie, może nie przenieść unique_ptr do inicjalizacji członka, ale to problem autora, Nie Ciebie. Niezależnie od tego, czy przechodzi przez wartość czy rvalue reference, Twój kod jest taki sam!

Jeśli piszesz bibliotekę, teraz wiesz, że powinieneś ją zagwarantować, więc po prostu to zrób, przejście przez referencję rvalue jest lepszym Wyborem niż wartość. Klient, który korzysta z biblioteki będzie po prostu napisz ten sam kod.

Teraz twoje pytanie. Jak przekazać argument unique_ptr do konstruktora lub funkcji? Wiesz, co jest najlepszym wyborem.

Http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html

 0
Author: merito,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-11 03:35:27