2012-12-13 22 views

Odpowiedz

15

Odpowiedź powinna być oczywista z specyfikacji standardu przypisania ruch w [unique.ptr.single.assign]/2:

efektów: Własność przekazywanych z u do *this jak wywołując reset(u.release()) a następnie przypisanie od std::forward<D>(u.get_deleter()).

Oczywiście przeniesienie zadania nie jest tożsame z reset(u.release()), ponieważ robi coś dodatkowego.

Dodatkowym efektem jest ważny, bez niego można dostać niezdefiniowane zachowanie z niestandardowych deleters:

#include <cstdlib> 
#include <memory> 

struct deleter 
{ 
    bool use_free; 
    template<typename T> 
    void operator()(T* p) const 
    { 
     if (use_free) 
     { 
     p->~T(); 
     std::free(p); 
     } 
     else 
     delete p; 
    } 
}; 

int main() 
{ 
    std::unique_ptr<int, deleter> p1((int*)std::malloc(sizeof(int)), deleter{true}); 
    std::unique_ptr<int, deleter> p2; 
    std::unique_ptr<int, deleter> p3; 

    p2 = std::move(p1); // OK 

    p3.reset(p2.release()); // UNDEFINED BEHAVIOUR! 
} 
5

Ten pierwszy może ostrzegać o niedopasowaniu destruktora. Ponadto, release() jest bardzo niebezpieczną funkcją, a twój trywialny przykład jest poprawny, ale wiele innych zastosowań nie jest. Najlepiej po prostu nigdy, nigdy nie używać tej funkcji.

+0

Czy możesz podać przykład, dlaczego jest tak niebezpieczny? – Ali

+0

Gdzie jest napisane w standardzie, do którego mogę zadzwonić, na przykład 'reset()' na 'p2' po' move() '? – Ali

+3

@Ali Ponieważ 'reset' nie ma żadnych warunków wstępnych, zawsze możesz go wywołać, ponieważ każdy ruch jest określony, aby pozostawić przeniesiony z obiektu w ** niezdefiniowanym, ale ważnym ** stanie, co powoduje użycie dowolnej funkcji bez warunki wstępne są zawsze ważne. To tylko wtedy, gdy polegamy na pewnych warunkach wstępnych (takich jak wskaźnik 'nullptr', ale może nawet jest to gwarantowane dla' std :: unique_ptr' według standardu), co wymaga najpierw sprawdzenia odpowiedniego warunku. Przenoszenie nie unieważnia obiektu w żaden sposób, to powszechne nieporozumienie. –

-2

Druga wersja może nie być wyjątkowo bezpieczna, jak sądzę. Jest to odpowiednik:

auto __tmp = p2.release(); 
p1.reset(__tmp); 

Zatem jeśli wywołanie std::unique_ptr::reset rzuca (co może mieć miejsce w przypadku, gdy delecja zarządzanego obiektu rzutów), następnie masz unreferred przedmiot, który nie zostanie zniszczony kiedykolwiek. W przypadku przeniesienia zadania, std::unique_ptr może (i powinien) czekać z rzeczywistym ruchem, aż oryginalny obiekt został zniszczony prawidłowo.

Należy jednak zauważyć, że jest to problem tylko wtedy, gdy destruktor zarządzanego obiektu może zostać rzucony, co prawie we wszystkich przypadkach jest złe, lub jeśli użyjemy niestandardowego deletera, który może wyrzucać. Tak więc w praktyce zazwyczaj nie ma żadnej różnicy w zachowaniu między tymi dwoma fragmentami kodu.


EDIT: W końcu Jonathan podkreśla w swoim komentarzu, że zwyczaj Deleter jest wymagane przez normę, aby nie rzucać, co rzeczywiście sprawia, że ​​rzucanie std::unique_ptr::reset całkiem prawdopodobne/non-conformant. Wskazuje on jednak, że istnieje inna różnica, ponieważ tylko przeniesienie przeniesienia powoduje również przesunięcie niestandardowych elementów, na które również napisał odpowiedź.


Jednak lekceważenie rzeczywistych zachowań wynikowych, istnieje ogromna różnica koncepcyjna między nimi. Jeśli przeniesienie jest odpowiednie, wykonaj przydział przeniesienia i spróbuj nie emulować go za pomocą innego kodu. W rzeczywistości nie mogę podać żadnego powodu, aby zastąpić pierwszy fragment kodu jeden-do-jednego przez sekundę. DeadMG ma rację, że std::unique_ptr::release powinien być używany tylko wtedy, gdy naprawdę wiesz, co robisz iw jakim kontekście masz do czynienia z niezarządzanymi dynamicznymi obiektami.

+0

_ "Tak więc w praktyce zazwyczaj nie ma żadnej różnicy w zachowaniu między tymi dwoma fragmentami kodu." To nieprawda, jeden przenosi deletera, a drugi nie. Jest to bardzo ważne, jeśli deleter jest stanowy. –

+2

No wiesz, operator przypisania ruchu 'std :: unique_ptr' jest określony jako" Przenosi własność z 'u' na' * this' tak, jakby wywołując 'reset (u.release())' [...] ". Standard polega w dużej mierze na tym, że destruktory nie rzucają. – Xeo

+0

@ Jonathan Wakely Hah, racja! Nie myślałem zbyt wiele o niestandardowych deletach. –

Powiązane problemy