2015-01-05 19 views
10

Powiedzmy, że mam klasę, która zarządza pamięcią i dlatego potrzebuje zdefiniowanych przez użytkownika funkcji specjalnych elementów (wyobraź sobie vector lub podobne).Implementacja przypisania ruchu w kategoriach konstruktora destruktora i ruchu

Rozważmy następujący realizację operatora move-przypisania:

Class& operator=(Class&& rhs) 
{ 
    this->~Class();     // call destructor 
    new (this) Class(std::move(rhs)); // call move constructor in-place 
} 
  1. Czy to ważny zaimplementować operator ruch przypisywania w ten sposób? Oznacza to, że wywołanie destruktora i konstruktora w ten sposób nie działa wbrew regułom życia w danym obiekcie w danym języku?

  2. Czy to dobry pomysł, aby zaimplementować w ten sposób operatora przeniesienia? Jeśli nie, dlaczego nie, i czy istnieje lepszy sposób kanoniczny?

+3

Co się stanie, jeśli '~ Class' lub' new (this) Class (std :: move (rhs)) wyrzuci? – Yakk

Odpowiedz

6

To nie jest prawidłowe: Co się stanie, jeśli to przeniesienie zostanie wywołane jako część przeniesienia obiektu podrzędnego? Następnie niszczymy dziecko (zakładając, że ma on wirtualny destruktor) i odtwarzamy na jego miejscu obiekt nadrzędny.

Powiedziałbym, że nawet w kontekście nie-wirtualnym nadal jest to zły pomysł, ponieważ nie widzisz często składni i może to utrudnić odczytanie kodu dla przyszłych opiekunów.

Najlepszym rozwiązaniem jest uniknięcie konieczności pisania własnego konstruktora ruchu (i korzystania z domyślnego) przez umożliwienie wszystkim członkom klasy poruszania się. Na przykład polegaj na unique_ptr, itp. W przeciwnym razie wydaje się, że implementacja w kategoriach swap (jako copy-and-swap do przypisania kopii) byłaby łatwym do zrozumienia mechanizmem.

2
  1. Może być ważny (1). Aby rozwiązać konkretny problem związany z życiem dtor/ctor, tak, to jest ważne (2). Tak działała oryginalna implementacja wektora.
  2. To może być dobry pomysł (prawdopodobnie nie jest), ale może nie chcieć kanoniczny sposób. (3)

(1) Nie ma kontrowersji, czy ruchy muszą być ważne w skrzynia z automatycznym ruchem. Argumenty przemawiające za bezpieczeństwem na własny ruch to pozycja, w której kod powinien być bezpieczny (duh), z pewnością oczekujemy, że samo przypisanie będzie bezpieczne. Również niektóre raporty z doświadczeń użytkowników, że dla wielu algorytmów, które używają ruchu, samodzielne przenoszenie jest możliwe i żmudne do sprawdzenia.

Argumenty przemawiające za bezpieczeństwem samodzielnego przemieszczania się to pozycja, w której semantyka całego punktu ruchu to oszczędność czasu i dlatego ruchy powinny być tak szybkie, jak to tylko możliwe. Samokontrola może być kosztowna w stosunku do kosztu przeniesienia. Zauważ, że kompilator nigdy nie wygeneruje kodu z automatycznym przeniesieniem, ponieważ naturalne wartości r (nie są rzutowane) nie mogą zostać przesunięte samodzielnie. Jedynym sposobem na samodzielne poruszanie się jest, gdy rzutowanie "std :: move()" jest jawnie wywoływane. Powoduje to obciążenie wywoływacza std :: move() w celu sprawdzenia, czy samo-ruch nie jest zaangażowany, lub przekonania się, że tak nie jest. Zauważ także, że byłoby trywialnie utworzyć zdefiniowany przez użytkownika odpowiednik "std :: move", który sprawdzałby dla self-move, a następnie nie zrobił nic. Jeśli nie popierasz samosiężenia, możesz to udokumentować.

(2) To nie jest szablon, dzięki czemu można się dowiedzieć, czy wyrażenie "new (this) Class (std :: move (rhs))" może rzucić. Jeśli może, to nie, to nie jest poprawne.

(3) Ten kod może być zagadką dla opiekunów, którzy mogą spodziewać się bardziej tradycyjne podejście wymiany, ale istnieje potencjalne wady podejścia swap. Jeśli zasoby wyzwalane przez cel muszą zostać zwolnione jak najszybciej (takie jak muteks), to zamiana ma tę wadę, że zasoby są zamieniane na obiekt źródłowy ruchu. Jeśli ruch jest wynikiem wywołania "std :: move()", obiekt źródłowy ruchu może nie zostać natychmiast usunięty. (Ponieważ nie jest to szablon, możesz wiedzieć, jakie zasoby są zwalniane.) Jeśli pamięć jest jedynym zasobem, który jest zwalniany, nie stanowi to problemu.)

Lepszym rozwiązaniem może być uwzględnienie uwolnienia zasobów kod z destruktora i kod przenoszący zasoby z konstruktora ruchu, a następnie po prostu wywołaj te (inline) procedury w tym operatorze przypisania ruchu.

Powiązane problemy