Operator move assignment i ' if (this!= & rhs)"

W operatorze przypisania klasy, zwykle musisz sprawdzić, czy przypisany obiekt jest obiektem wywołującym, więc nie spieprzysz tego:

Class& Class::operator=(const Class& rhs) {
    if (this != &rhs) {
        // do the assignment
    }

    return *this;
}

Czy potrzebujesz tego samego dla operatora przydziału ruchu? Czy jest kiedykolwiek sytuacja, w której this == &rhs byłaby prawdziwa?

? Class::operator=(Class&& rhs) {
    ?
}
Author: Seth Carnegie, 2012-02-17

6 answers

Wow, jest tu tyle do posprzątania...

Po pierwsze, Copy and Swap nie zawsze jest prawidłowym sposobem implementacji przypisania kopii. Prawie na pewno w przypadku dumb_array jest to rozwiązanie nieoptymalne.

Użycie Copy and Swap jest dla dumb_array to klasyczny przykład umieszczenia najdroższej operacji z najpełniejszymi funkcjami w dolnej warstwie. Jest idealny dla klientów, którzy chcą pełnię funkcji i są gotowi zapłacić kara za występ. Dostają dokładnie to, czego chcą.

Ale jest to katastrofalne dla klientów, którzy nie potrzebują najpełniejszej funkcji, a zamiast tego szukają najwyższej wydajności. Dla nich dumb_array jest tylko kolejnym oprogramowaniem, które muszą przepisać, ponieważ jest zbyt wolne. Gdyby dumb_array został zaprojektowany inaczej, mógłby zadowolić obu klientów bez kompromisów dla żadnego klienta.

Kluczem do zadowolenia obu klientów jest zbudowanie najszybszych operacji w najniższy poziom, a następnie dodać API do tego dla pełniejszych funkcji przy większych kosztach. Czyli potrzebujesz silnej gwarancji wyjątku, dobrze, płacisz za to. Nie potrzebujesz tego? Oto szybsze rozwiązanie.

Bądźmy konkretni: oto szybki, podstawowy operator przypisania wyjątków dla dumb_array:

dumb_array& operator=(const dumb_array& other)
{
    if (this != &other)
    {
        if (mSize != other.mSize)
        {
            delete [] mArray;
            mArray = nullptr;
            mArray = other.mSize ? new int[other.mSize] : nullptr;
            mSize = other.mSize;
        }
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }
    return *this;
}

Wyjaśnienie:

[54]}jedną z droższych rzeczy, które można zrobić na nowoczesnym sprzęcie, jest wycieczka do sterty. Wszystko, co można zrobić, aby uniknąć podróży do sterta to Dobrze Spędzony Czas i wysiłek. Klienci dumb_array mogą chcieć często przypisywać tablice o tym samym rozmiarze. A kiedy to zrobią, wszystko, co musisz zrobić, to memcpy (ukryty pod std::copy). Nie chcesz przydzielać nowej tablicy o tym samym rozmiarze, a następnie dealokować starej tablicy o tym samym rozmiarze! Teraz dla Twoich klientów, którzy naprawdę chcą silnego bezpieczeństwa WYJĄTKÓW:]}
template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    swap(lhs, rhs);
    return lhs;
}

A może jeśli chcesz skorzystać z move assignment w C++11 to powinno być:

template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    lhs = std::move(rhs);
    return lhs;
}

Jeśli dumb_array'S klienci cenią szybkość, powinni wywołać operator=. Jeśli potrzebują silnego bezpieczeństwa WYJĄTKÓW, mogą wywoływać algorytmy generyczne, które będą działać na wielu różnych obiektach i muszą być zaimplementowane tylko raz.

A teraz wracając do pierwotnego pytania (które w tym momencie mA Typ-o):

Class&
Class::operator=(Class&& rhs)
{
    if (this == &rhs)  // is this check needed?
    {
       // ...
    }
    return *this;
}
To jest kontrowersyjne pytanie. Niektórzy powiedzą Tak, absolutnie, niektórzy powiedzą nie.

Moje osobiste zdanie jest nie, nie potrzebujesz tego szach.

Uzasadnienie:

Gdy obiekt wiąże się z referencją rvalue, jest to jedna z dwóch rzeczy:

  1. tymczasowy.
  2. Obiekt, w który dzwoniący chce, byś uwierzył, jest tymczasowy.

Jeśli masz odniesienie do obiektu, który jest rzeczywistym tymczasowym, to z definicji masz unikalne odniesienie do tego obiektu. Nigdzie indziej w twoim programie nie może się do niego odwoływać. I. e. this == &temporary nie jest możliwe .

Teraz jeśli twój Klient okłamał cię i obiecał, że dostaniesz tymczasowe, gdy nie jesteś, to obowiązkiem klienta jest upewnienie się, że nie musisz się tym przejmować. Jeśli chcesz być naprawdę ostrożny, wierzę, że byłaby to lepsza implementacja: {]}

Class&
Class::operator=(Class&& other)
{
    assert(this != &other);
    // ...
    return *this;
}

Tzn. jeślizostały przekazane referencji SELF, jest to błąd ze strony klienta, który powinien zostać naprawiony.

Dla kompletności, oto operator przypisania ruchu dla dumb_array:

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

W typowym przypadku użycia przypisania move, {[26] } będzie obiektem typu move-from, a więc delete [] mArray; powinno być no-op. bardzo ważne jest, aby implementacje wykonywały delete na nullptr tak szybko, jak to możliwe.

Zastrzeżenie:

Niektórzy będą twierdzić, że to dobry pomysł, albo tylko zło konieczne. A to, jeśli swap przejdzie do domyślnego swapu, może spowodować przypisanie własnego ruchu.

Nie zgadzam się, że swap(x, x) jest zawsze dobrym pomysłem. If found in my own kod, Uznam to za błąd wydajności i naprawię. Ale jeśli chcesz na to pozwolić, zdaj sobie sprawę, że swap(x, x) self-move-assignemnet przypisuje tylko wartość moved-from. I w naszym przykładzie dumb_array będzie to całkowicie nieszkodliwe, jeśli po prostu pominiemy twierdzenie lub ograniczymy je do sprawy przesuniętej:

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other || mSize == 0);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

Jeśli samodzielnie przypisujesz dwa przesunięte-z (puste) dumb_array, nie robisz nic nieprawidłowego poza wstawianiem bezużytecznych instrukcji do swojego programu. Ta sama obserwacja może być dokonana dla zdecydowana większość obiektów.

<Aktualizacja>

Przemyślałem tę kwestię i nieco zmieniłem swoje stanowisko. Obecnie uważam, że przypisanie powinno być tolerancyjne dla przypisania własnego, ale warunki postu przy przypisaniu kopii i przeniesieniu są różne: {]}

Dla przypisania kopii:

x = y;

Należy mieć warunek, że wartość y nie powinna być zmieniana. Gdy &x == &y to ten posttransformuje into: self copy assignment should have no impact on the value of x.

Dla przypisania ruchu:

x = std::move(y);

Jeden powinien mieć warunek post, że y ma ważny, ale nieokreślony stan. Gdy &x == &y to ten postkondition tłumaczy się na: x ma poprawny, ale nieokreślony stan. Czyli samo zadanie nie musi być no-op. ale nie powinno się rozbić. W tym celu należy wykonać następujące czynności:]}

template <class T>
void
swap(T& x, T& y)
{
    // assume &x == &y
    T tmp(std::move(x));
    // x and y now have a valid but unspecified state
    x = std::move(y);
    // x and y still have a valid but unspecified state
    y = std::move(tmp);
    // x and y have the value of tmp, which is the value they had on entry
}

Powyższe działa, jak dopóki x = std::move(x) nie rozbije się. Może pozostawić x w dowolnym ważnym, ale nieokreślonym stanie.

Widzę trzy sposoby zaprogramowania operatora przydziału ruchu dla dumb_array, aby to osiągnąć:

dumb_array& operator=(dumb_array&& other)
{
    delete [] mArray;
    // set *this to a valid state before continuing
    mSize = 0;
    mArray = nullptr;
    // *this is now in a valid state, continue with move assignment
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

Powyższa implementacja toleruje SELF assignment, ale *this i other kończą jako tablica o rozmiarze zerowym po self-move assignment, bez względu na pierwotną wartość *this. W porządku.

dumb_array& operator=(dumb_array&& other)
{
    if (this != &other)
    {
        delete [] mArray;
        mSize = other.mSize;
        mArray = other.mArray;
        other.mSize = 0;
        other.mArray = nullptr;
    }
    return *this;
}

Powyższa implementacja toleruje SELF assignment tak samo jak operator copy assignment robi, czyniąc go no-op. to również jest w porządku.

dumb_array& operator=(dumb_array&& other)
{
    swap(other);
    return *this;
}

Powyższe jest w porządku tylko wtedy, gdy dumb_array nie przechowuje zasobów, które powinny zostać zniszczone "natychmiast". Na przykład, jeśli jedynym zasobem jest pamięć, powyższe jest w porządku. Jeśli dumb_array może posiadać zamki mutex lub stan otwarty plików, klient może oczekiwać natychmiastowego uwolnienia tych zasobów na lhs przypisania move, a zatem implementacja ta może być problematyczne.

Koszt pierwszego to dwa dodatkowe sklepy. Koszt drugiego to test-and-branch. Oba działają. Oba spełniają wszystkie wymagania tabeli 22 MoveAssignable w standardzie C++11. Trzeci działa również modulo problem braku pamięci-zasobów.

Wszystkie trzy implementacje mogą mieć różne koszty w zależności od sprzętu: jak droga jest gałąź? Jest dużo rejestrów czy bardzo mało?

The take-away is that self-move-assignment, w przeciwieństwie do self-copy-assignment, nie musi zachować bieżącej wartości.

</Aktualizacja>

Ostatnia (miejmy nadzieję) edycja zainspirowana komentarzem Luca Dantona:

Jeśli piszesz klasę wysokiego poziomu, która nie zarządza bezpośrednio pamięcią (ale może mieć bazy lub członków, które to robią), to najlepszą implementacją przypisania move jest często:

Class& operator=(Class&&) = default;

To przeniesie każdą bazę i każdego Członka po kolei, a nie Dołącz this != &other czek. Zapewni to najwyższą wydajność i podstawowe bezpieczeństwo WYJĄTKÓW, zakładając, że nie trzeba utrzymywać niezmienników wśród Twoich baz i członków. Dla klientów wymagających silnego bezpieczeństwa WYJĄTKÓW, skieruj ich w stronę strong_assign.

 120
Author: Howard Hinnant,
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:29

Najpierw pomyliłeś podpis operatora przyporządkowania ruchu. Ponieważ moves kradnie zasoby z obiektu źródłowego, źródłem musi być odniesienie nie-const r-value reference.

Class &Class::operator=( Class &&rhs ) {
    //...
    return *this;
}

Zauważ, że nadal wracasz przez (non-const) l - odniesienie do wartości.

Dla każdego typu bezpośredniego przydziału, standardem nie jest sprawdzanie samo-przydziału, ale upewnienie się, że samo-przydział nie spowoduje awarii i spalenia. Ogólnie nikt nie robi x = x ani y = std::move(y) wywołania, ale aliasing, zwłaszcza poprzez wiele funkcji, może prowadzić a = b lub c = std::move(d) do samodzielnego przypisania. Jawna Kontrola samo-przypisania, tj. this == &rhs, która pomija mięso funkcji, gdy prawda jest jednym ze sposobów zapewnienia bezpieczeństwa samo-przypisania. Ale jest to jeden z najgorszych sposobów, ponieważ optymalizuje (miejmy nadzieję) rzadki przypadek, podczas gdy jest to anty-Optymalizacja dla bardziej powszechnego przypadku (z powodu rozgałęzień i ewentualnie braków w pamięci podręcznej).

Teraz, gdy (przynajmniej) jeden z operandów jest bezpośrednio obiekt tymczasowy, nigdy nie można mieć scenariusza samo-przypisania. Niektórzy opowiadają się za założeniem tego przypadku i optymalizują kod do tego tak bardzo, że kod staje się samobójczo głupi, gdy założenie jest błędne. Mówię, że wyrzucanie tych samych przedmiotów na użytkowników jest nieodpowiedzialne. Nie robimy tego argumentu dla copy-assignment; po co odwracać pozycję dla move-assignment?

Zróbmy przykład, zmieniony z innego respondenta:

dumb_array& dumb_array::operator=(const dumb_array& other)
{
    if (mSize != other.mSize)
    {
        delete [] mArray;
        mArray = nullptr;  // clear this...
        mSize = 0u;        // ...and this in case the next line throws
        mArray = other.mSize ? new int[other.mSize] : nullptr;
        mSize = other.mSize;
    }
    std::copy(other.mArray, other.mArray + mSize, mArray);
    return *this;
}

This copy-assignment radzi sobie z samodzielnym przypisaniem bez wyraźnej kontroli. Jeśli rozmiar źródła i miejsca docelowego jest różny, dealokacja i realokacja poprzedzają kopiowanie. W przeciwnym razie wystarczy kopiowanie. Self-assignment nie otrzymuje zoptymalizowanej ścieżki, jest wrzucany do tej samej ścieżki, co wtedy, gdy rozmiar źródła i miejsca docelowego zaczyna być równy. Kopiowanie jest technicznie niepotrzebne, gdy dwa obiekty są równoważne (w tym gdy są tym samym obiektem), ale taka jest cena, gdy nie robisz sprawdzanie równości (pod względem wartości lub adresu), ponieważ samo sprawdzanie byłoby stratą większości czasu. Zauważ, że self-assignment obiektu spowoduje serię SELF-assignmentów na poziomie elementu; Typ elementu musi być bezpieczny, aby to zrobić.

Podobnie jak w przykładzie źródłowym, to przypisanie kopii zapewnia podstawową gwarancję bezpieczeństwa WYJĄTKÓW. Jeśli chcesz mieć silną gwarancję, użyj operatora Unified-assignment z oryginalnego zapytania Copy and Swap , który obsługuje zarówno kopiowanie, jak i przenoszenie. Ale celem tego przykładu jest zmniejszenie bezpieczeństwa o jedną rangę, aby zyskać prędkość. (BTW, Zakładamy, że wartości poszczególnych elementów są niezależne; że nie ma niezmiennego ograniczenia ograniczającego niektóre wartości w porównaniu z innymi.)

Spójrzmy na przypisanie ruchu dla tego samego typu:

class dumb_array
{
    //...
    void swap(dumb_array& other) noexcept
    {
        // Just in case we add UDT members later
        using std::swap;

        // both members are built-in types -> never throw
        swap( this->mArray, other.mArray );
        swap( this->mSize, other.mSize );
    }

    dumb_array& operator=(dumb_array&& other) noexcept
    {
        this->swap( other );
        return *this;
    }
    //...
};

void  swap( dumb_array &l, dumb_array &r ) noexcept  { l.swap( r ); }

Typ wymienny, który wymaga dostosowania, powinien mieć dwuargumentową wolną funkcję o nazwie swap w tej samej przestrzeni nazw co Typ. (Przestrzeń nazw ograniczenie pozwala na zamianę niewykwalifikowanych wywołań na pracę.) Typ kontenera powinien również dodać funkcję Public swap member, aby pasowała do standardowych kontenerów. Jeśli członek swap nie jest podany, to funkcja wolna swap prawdopodobnie musi być oznaczona jako przyjaciel typu swappable. Jeśli dostosujesz ruchy do użycia swap, musisz podać swój własny kod zamiany; standardowy kod wywołuje kod ruchu typu, co skutkowałoby nieskończoną wzajemną rekurencją dla move-customized typy.

Podobnie jak destruktory, funkcje zamiany i operacje przeniesienia nie powinny być nigdy rzucane, jeśli w ogóle jest to możliwe, i prawdopodobnie oznaczone jako takie (W C++11). Standardowe typy bibliotek i procedury mają optymalizacje dla typów ruchomych, których nie można wyrzucić.

Ta pierwsza wersja Move-assignment spełnia podstawowy kontrakt. Znaczniki zasobów źródłowych są przenoszone do obiektu docelowego. Stare zasoby nie zostaną wycieknięte, ponieważ obiekt źródłowy nimi zarządza. Oraz obiekt źródłowy pozostaje w stanie użytkowym, w którym można zastosować do niego dalsze operacje, w tym przypisanie i zniszczenie.

Zauważ, że to przeniesienie-przypisanie jest automatycznie bezpieczne dla samodzielnego przypisania, ponieważ swap wywołanie jest. Jest również zdecydowanie bezpieczny dla WYJĄTKÓW. Problemem jest niepotrzebne przechowywanie zasobów. Stare zasoby dla celu docelowego nie są już koncepcyjnie potrzebne, ale tutaj nadal są wokół tylko po to, aby obiekt źródłowy mógł pozostać ważny. Jeśli planowane zniszczenie obiekt źródłowy jest daleko, marnujemy miejsce na zasoby, lub co gorsza, jeśli całkowita przestrzeń zasobów jest ograniczona i inne petycje dotyczące zasobów będą miały miejsce, zanim (nowy) obiekt źródłowy oficjalnie umrze.

Ta kwestia jest przyczyną kontrowersyjnych aktualnych porad guru dotyczących samonaprowadzania podczas przydzielania ruchu. Sposób pisania move-assignment bez przeciągania zasobów jest taki jak:

class dumb_array
{
    //...
    dumb_array& operator=(dumb_array&& other) noexcept
    {
        delete [] this->mArray;  // kill old resources
        this->mArray = other.mArray;
        this->mSize = other.mSize;
        other.mArray = nullptr;  // reset source
        other.mSize = 0u;
        return *this;
    }
    //...
};

Źródło jest resetowane do domyślnych warunków, podczas gdy stare miejsce docelowe zasoby są niszczone. W przypadku samodzielnego przypisania, Twój obecny obiekt popełnia samobójstwo. Głównym sposobem obejścia tego problemu jest otaczanie kodu akcji blokiem if(this != &other) lub wkręcanie go i pozwalanie klientom zjeść assert(this != &other) początkową linię (jeśli czujesz się dobrze).

Alternatywą jest zbadanie, jak sprawić, by wyjątek copy-assignment był bezpieczny, bez unified-assignment, i zastosowanie go do move-assignment:]}
class dumb_array
{
    //...
    dumb_array& operator=(dumb_array&& other) noexcept
    {
        dumb_array  temp{ std::move(other) };

        this->swap( temp );
        return *this;
    }
    //...
};

Gdy other i this są odrębne, other jest opróżniane przez przenieś do temp i tak pozostanie. Następnie this traci swoje stare zasoby na temp, podczas gdy zasoby pierwotnie posiadane przez other. Wtedy stare zasoby this giną, kiedy temp to zrobi.

Kiedy dochodzi do samodzielnego przypisania, opróżnianie other do temp również opróżnia this. Następnie obiekt docelowy odzyskuje swoje zasoby po wymianie temp i this. Śmierć temp twierdzi, że pusty obiekt, który powinien być praktycznie nie-op. The this/other obiekt zachowuje swoje zasoby.

Przypisanie do ruchu nie powinno być nigdy rzucane tak długo, jak budowa ruchu i zamiana są również. Koszt bezpieczeństwa podczas samodzielnego przydzielania to jeszcze kilka instrukcji w stosunku do typów niskopoziomowych, które powinny być zawalone wywołaniem dealokacji.

 11
Author: CTMacUser,
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 11:47:19

Jestem w obozie tych, którzy chcą operatorów bezpiecznych do samodzielnego przypisania, ale nie chcą pisać sprawdzeń do samodzielnego przypisania w implementacjach operator=. W rzeczywistości nawet nie chcę implementować operator=, chcę, aby domyślne zachowanie działało "od razu po wyjęciu z pudełka". Najlepsi członkowie specjalni to ci, którzy przychodzą za darmo.

To powiedziawszy, wymagania MoveAssignable obecne w standardzie są opisane w następujący sposób (z 17.6.3.1 wymagania argumentu szablonu [użyteczność.arg.wymagania], n3290):

Expression  Return type Return value    Post-condition
t = rv      T&          t               t is equivalent to the value of rv before the assignment

Gdzie symbole zastępcze są opisane jako: "t [jest modyfikowaną wartością lvalue typu T; "oraz" rv jest wartością rvalue typu T;". Zauważ, że są to wymagania stawiane typom używanym jako argumenty do szablonów biblioteki standardowej, ale patrząc gdzie indziej w standardzie zauważam, że każde wymaganie przy przypisywaniu ruchu jest podobne do tego.

Oznacza to, że {[5] } musi być "bezpieczny". Jeśli potrzebujesz testu tożsamości (np. this != &other), a następnie go dla niego, albo nie będzie nawet w stanie umieścić swoje przedmioty w std::vector! (Chyba że nie używasz tych członków / operacji, które wymagają MoveAssignable; ale nieważne.) Zauważ, że w poprzednim przykładzie a = std::move(a), wtedy this == &other rzeczywiście się utrzyma.

 6
Author: Luc Danton,
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
2012-02-17 04:12:34

Ponieważ Twoja bieżąca funkcja operator= jest zapisywana, ponieważ stworzyłeś argument rvalue-reference const, nie ma możliwości, abyś "ukradł" wskaźniki i zmienił wartości przychodzącego odniesienia rvalue... po prostu nie można tego zmienić, można tylko czytać z niego. Dostrzegłbym problem tylko wtedy, gdybyś zaczął wywoływać delete na wskaźnikach itp. w twoim obiekcie this, tak jak w normalnej metodzie lvaue-reference operator=, ale taki rodzaj pokonuje punkt wersji rvalue ... tj. to wydaje się zbędne używanie wersji rvalue do wykonywania tych samych operacji normalnie pozostawionych metodzie const-lvalue operator=.

Teraz, jeśli zdefiniowałeś operator=, aby wziąć nie - const rvalue-reference, wtedy jedynym sposobem, w jaki mogłem zobaczyć, że wymagane jest sprawdzenie, było przekazanie obiektu this do funkcji, która celowo zwróciła referencję rvalue, a nie tymczasową.

Na przykład, załóżmy, że ktoś próbował napisać operator+ funkcję i wykorzystać mieszankę rvalue odniesienia i odniesienia lvalue w celu "zapobieżenia" tworzeniu dodatkowych tymczasowych obiektów podczas niektórych operacji dodawania stosu na obiekcie typu:

struct A; //defines operator=(A&& rhs) where it will "steal" the pointers
          //of rhs and set the original pointers of rhs to NULL

A&& operator+(A& rhs, A&& lhs)
{
    //...code

    return std::move(rhs);
}

A&& operator+(A&& rhs, A&&lhs)
{
    //...code

    return std::move(rhs);
}

int main()
{
    A a;

    a = (a + A()) + A(); //calls operator=(A&&) with reference bound to a

    //...rest of code
}

Teraz, z tego co rozumiem o odniesieniach rvalue, Robienie powyższego jest zniechęcające (tj. powinieneś po prostu zwrócić tymczasowe, a nie rvalue reference), ale jeśli ktoś nadal to robi, powinieneś sprawdzić, czy przychodzące rvalue-reference nie odwołuje się do tego samego obiektu co Wskaźnik this.

 2
Author: Jason,
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
2012-02-17 03:38:12

Moja odpowiedź jest wciąż taka, że przypisanie ruchu nie musi być ratowane przed samoobsługą, ale ma inne wyjaśnienie. Rozważmy std:: unique_ptr. Gdybym miał taką wdrożyć, zrobiłbym coś takiego:

unique_ptr& operator=(unique_ptr&& x) {
  delete ptr_;
  ptr_ = x.ptr_;
  x.ptr_ = nullptr;
  return *this;
}
Jeśli spojrzysz na Scotta Meyersa, który to wyjaśnia, robi coś podobnego. (Jeśli wędrować dlaczego nie zrobić swap-ma jeden dodatkowy zapis). A to nie jest bezpieczne dla siebie. Czasami jest to niefortunne. Rozważmy wyprowadzenie z wektora wszystkich liczby parzyste:
src.erase(
  std::partition_copy(src.begin(), src.end(),
                      src.begin(),
                      std::back_inserter(even),
                      [](int num) { return num % 2; }
                      ).first,
  src.end());

To jest ok dla liczb całkowitych, ale nie wierzę, że można zrobić coś takiego z semantyką ruchu.

Podsumowując: przeniesienie przypisania do samego obiektu nie jest w porządku i trzeba na to uważać.

Mała aktualizacja.
  1. nie zgadzam się z Howardem, co jest złym pomysłem, ale i tak-myślę, że samo ruszanie się przypisanie" wyprowadzonych " obiektów powinno zadziałać, ponieważ swap(x, x) powinno zadziałać. Algorytmy uwielbiają takie rzeczy! Zawsze jest miło, gdy kącik sprawa po prostu działa. (I jeszcze nie widzę przypadku, w którym nie jest za darmo. Nie znaczy jednak, że nie istnieje).
  2. tak zaimplementowano przypisywanie unique_ptrs w libc++: unique_ptr& operator=(unique_ptr&& u) noexcept { reset(u.release()); ...} Jest bezpieczny do samodzielnego przemieszczania się.
  3. podstawowe wytyczne myślę, że powinno być OK, aby przypisać własny ruch.
 1
Author: Denis Yaroshevskiy,
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-02-15 21:51:23

Jest taka sytuacja ,o której mogę pomyśleć. Za to stwierdzenie: Myclass obj; std:: move (obj) = std:: move (obj)

 0
Author: little_monster,
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-08-17 10:45:45