Dla std::unique_ptr
s p1
i p2
, jakie są różnice między std::move()
i std::unique_ptr::reset()
?Jakie są różnice między std :: move i unique_ptr :: reset?
p1 = std::move(p2);
p1.reset(p2.release());
Dla std::unique_ptr
s p1
i p2
, jakie są różnice między std::move()
i std::unique_ptr::reset()
?Jakie są różnice między std :: move i unique_ptr :: reset?
p1 = std::move(p2);
p1.reset(p2.release());
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ącreset(u.release())
a następnie przypisanie odstd::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!
}
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.
Czy możesz podać przykład, dlaczego jest tak niebezpieczny? – Ali
Gdzie jest napisane w standardzie, do którego mogę zadzwonić, na przykład 'reset()' na 'p2' po' move() '? – Ali
@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. –
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.
_ "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. –
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
@ Jonathan Wakely Hah, racja! Nie myślałem zbyt wiele o niestandardowych deletach. –
TL; DR: The 2nd forma jest zepsuty. Nigdy go nie używaj. –