Przeglądałem kod źródłowy Clang i znalazłem ten fragment:
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = std::move(Value);
}
Dlaczego miałbym chcieć ?std::move
std::shared_ptr
Czy jest jakiś sens przenoszenia własności do udostępnionego zasobu?
Dlaczego nie miałbym po prostu tego zrobić?
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = Value;
}
źródło
Używając
move
unikasz zwiększania, a następnie natychmiastowego zmniejszania liczby udziałów. To może zaoszczędzić ci drogich operacji atomowych na liczniku użycia.źródło
Operacje przenoszenia (takie jak konstruktor przenoszenia)
std::shared_ptr
są tanie , ponieważ w zasadzie są „wskaźnikami kradzieży” (ze źródła do celu; dokładniej mówiąc, cały blok kontroli stanu jest „skradziony” od źródła do celu, w tym informacje o liczbie referencyjnej) .Zamiast tego operacje kopiowania przy
std::shared_ptr
wywołaniu niepodzielnego zwiększenia liczby referencji (tj. Nie tylko++RefCount
na składnikuRefCount
danych całkowitych , ale np. WywołanieInterlockedIncrement
w systemie Windows) jest droższe niż zwykła kradzież wskaźników / stanu.Tak więc, analizując szczegółowo dynamikę liczby ref w tym przypadku:
Jeśli przekażesz
sp
wartość, a następnie weźmiesz kopię wewnątrzCompilerInstance::setInvocation
metody, masz:shared_ptr
parametr jest konstruowany jako kopia: ref count atomowy przyrost .shared_ptr
parametr w elemencie danych: Ref liczyć atomową przyrost .shared_ptr
parametr zostaje zniszczony: ref count atomic decment .Masz dwa atomowe przyrosty i jeden atomowy dekrement, co daje w sumie trzy atomowe operacje.
Zamiast tego, jeśli przekażesz
shared_ptr
parametr według wartości, a następniestd::move
wewnątrz metody (tak jak zostało to prawidłowo zrobione w kodzie Clanga), masz:shared_ptr
parametr jest konstruowany jako kopia: ref count atomowy przyrost .std::move
umieszczaszshared_ptr
parametr w składniku danych: liczba ref nie zmienia się! Po prostu kradniesz wskaźniki / stan: nie są zaangażowane żadne kosztowne atomowe operacje liczenia referencji.shared_ptr
parametr zostaje zniszczony; ale ponieważ przeszedłeś w kroku 2, nie ma nic do zniszczenia, ponieważshared_ptr
parametr już na nic nie wskazuje. Ponownie, w tym przypadku nie dochodzi do dekrementacji atomowej.Konkluzja: w tym przypadku otrzymujesz tylko jeden atomowy przyrost liczby ref, tj. Tylko jedną atomową operację.
Jak widać, jest to znacznie lepsze niż dwa atomowe przyrosty plus jeden atomowy dekrement (w sumie trzy atomowe operacje) dla wielkości kopii.
źródło
compilerInstance.setInvocation(std::move(sp));
nie będzie żadnego przyrostu . Możesz uzyskać to samo zachowanie, dodając przeciążenie, które zajmujeshared_ptr<>&&
ale po co duplikować, gdy nie jest to konieczne.setInvocation(new CompilerInvocation)
lub jak wspomniano w programie zapadkowymsetInvocation(std::move(sp))
. Przepraszam, jeśli mój pierwszy komentarz był niejasny, faktycznie opublikowałem go przez przypadek, zanim skończyłem pisać, i postanowiłem go po prostu zostawićKopiowanie
shared_ptr
obejmuje kopiowanie wewnętrznego wskaźnika obiektu stanu i zmianę liczby odwołań. Przeniesienie obejmuje tylko zamianę wskaźników do wewnętrznego licznika odwołań i posiadanego obiektu, więc jest szybsze.źródło
Istnieją dwa powody używania std :: move w tej sytuacji. Większość odpowiedzi dotyczyła kwestii szybkości, ale zignorowała ważną kwestię wyraźniejszego pokazania intencji kodu.
Dla std :: shared_ptr, std :: move jednoznacznie oznacza przeniesienie własności wskazanego, podczas gdy prosta operacja kopiowania dodaje dodatkowego właściciela. Oczywiście, jeśli pierwotny właściciel zrzeka się później swojej własności (na przykład pozwalając na zniszczenie ich std :: shared_ptr), wówczas przeniesienie własności zostało zrealizowane.
Kiedy przenosisz własność za pomocą std :: move, oczywiste jest, co się dzieje. Jeśli używasz zwykłej kopii, nie jest oczywiste, że zamierzoną operacją jest przeniesienie, dopóki nie zweryfikujesz, że pierwotny właściciel natychmiast zrzeka się prawa własności. Jako bonus możliwa jest wydajniejsza implementacja, ponieważ atomowe przeniesienie własności może uniknąć stanu tymczasowego, w którym liczba właścicieli wzrosła o jednego (i związane z tym zmiany w liczbie referencyjnej).
źródło
Przynajmniej z libstdc ++ powinieneś uzyskać taką samą wydajność z przenoszeniem i przypisywaniem, ponieważ
operator=
wywołujestd::move
przychodzący wskaźnik. Zobacz: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr.h#L384źródło