2012-10-29 12 views
9

Tu jest możliwe określenie std::swap:Na realizację std :: swapa pod względem przypisania ruchu i poruszać konstruktora

template<class T> 
void swap(T& a, T& b) { 
    T tmp(std::move(a)); 
    a = std::move(b); 
    b = std::move(tmp); 
} 

wierzę, że

  1. std::swap(v,v) gwarantuje nie mają skutków i
  2. std::swap można zaimplementować jak wyżej.

Poniższy cytat wydaje mi się sugerować, że te przekonania są sprzeczne.

17.6.4.9 Argumenty funkcji [res.on.arguments]

1 Każdy z poniższych odnosi się do wszystkich argumentów do funkcji zdefiniowanych w C++ biblioteki standardowej, o ile wyraźnie nie zaznaczono inaczej.

...

  • Jeśli argumentem funkcji wiąże się z parametrem odniesienia rvalue, implementacja może zakładać, że parametr ten jest unikalnym odniesienie do ten argument. [Uwaga: Jeśli parametr jest parametrem generycznym postaci T & & i l wartość typu A jest powiązany, argument jest powiązany z wartością odniesienia o wartości l (14.8.2.1) i dlatego nie jest objęty poprzednim zdaniem . - nota końcowa] [Uwaga: Jeśli program rzuci wartość l do wartości o wartości , przekazując tę ​​lwartośc do funkcji bibliotecznej (np. Przez wywołanie funkcji z argumentem move (x)), program jest skutecznie prosząc tę ​​funkcję do traktuj tę lwartość jako tymczasową. Implementacja jest bezpłatna, aby zoptymalizować kontrole aliasingu z zewnątrz, które mogą być potrzebne, jeśli argument był l-wartości. -endnote]

(dzięki Howard Hinnant dla providing the quote)

Niech v być przedmiotem pewnego rodzaju ruchomego pobranej od Standard Template Library i rozważyć połączenie std::swap(v, v). W powyższej linii a = std::move(b); jest to przypadek wewnątrz T::operator=(T&& t), który jest , więc parametr to , a nie unikalne odniesienie. Jest to naruszenie powyższego wymogu, więc linia a = std::move(b) wywołuje niezdefiniowane zachowanie po wywołaniu z std::swap(v, v).

Jakie jest wyjaśnienie tutaj?

+0

* Jakie jest wytłumaczenie? * Czego wymaga wyjaśnienie? –

+0

@ DavidRodríguez-dribeas Przyjąłem dwie rzeczy i doszedłem do sprzeczności - 'std :: move (v, v)' gwarantuje, że nic nie robi, a także nieokreślone zachowanie. Te dwa stwierdzenia nie mogą mieć obu racji. Coś jest nie tak z moim rozumowaniem lub z założeniami. Które coś jest nie tak? –

+0

Masz na myśli 'std :: swap (v, v)', a nie 'std :: move (v, v)', prawda? –

Odpowiedz

0

Uważam, że nie jest ważne, ponieważ definicja std::swapstd::swap definiuje wziąć lwartości referencje, a nie referencje rvalue (20.2.2 [utility.swap])

+0

Dzięki za wskazanie błędu. Zaktualizowałem pytanie. Jednak poprawienie błędu nie wydaje mi się odpowiedzią na problem. –

+1

Ta sama różnica: 'template void swap (T & a, T & b) {T tmp (std :: move (a)); a = std :: move (b); b = std :: move (tmp); } ' –

3

Następnie wyrażenie a = std::move(b); zostanie wykonany, celem jest już puste, w stanie, w którym tylko zniszczenie jest dobrze zdefiniowane. To będzie skutecznie no-op, ponieważ obiekt po lewej i prawej stronie jest już pusty.Stan obiektu po ruchu jest nadal nieznany, ale możliwy do zniszczenia. Następna instrukcja przenosi zawartość z powrotem z tmp i przywraca obiekt do znanego stanu.

+1

W jaki sposób pusty obiekt rozwiązuje sprzeczność w pytaniu? Cytat z pytania nie wydaje mi się dopuszczać wyjątku dla pustych obiektów. –

+0

@ BjarkeH.Roune: Weź pod uwagę kod: 'int i; i = i; i = 5; '. Instrukcja środkowa odczytuje niezainicjowaną zmienną, ale nie ma to wpływu na poprawność deklaracji lub późniejszego przypisania. Cytat stwierdza, że ​​implementacja może zakładać, że nie ma aliasingu (tj. 'This' i' rhs' to różne obiekty), co z kolei oznacza, że ​​może on swobodnie uwolnić zasoby, a następnie pobrać je z argumentu, który byłoby problematyczne w przypadku ogólnym, ale w tym przypadku poprzednia linia już * zajęła * zasoby. –

+0

@ BjarkeH.Roune: [...] Zauważ, że cytat nie mówi nawet, że niezdefiniowane zachowanie przesyła obiekty aliasingowe, tyle tylko, że implementacja może swobodnie zakładać, że aliasing nie nastąpił.Do czasu wykonania drugiego wiersza rzeczywiste treści są przechowywane w innym miejscu, więc wyrażenie nie ma na nie wpływu. Gwarancje na obiekcie przed i po wyrażeniu są dokładnie takie same i muszą być utrzymywane niezależnie od potencjalnego aliasingu: obiekt musi być zniszczalny i to wszystko, co jest potrzebne do trzeciego zdania. –

6

[res.on.arguments] to oświadczenie o tym, jak klient powinien używać std :: lib. Kiedy klient wysyła xvalue do funkcji std :: lib, klient musi chcieć udawać, że xvalue jest naprawdę wartością pryncypalną i oczekiwać, że std :: lib to wykorzysta.

Jednak, gdy klient wywołuje std :: swap (x, x), klient nie wysyła wartości x do funkcji std :: lib. Jest to implementacja, która robi to zamiast tego. W związku z tym obowiązkiem jest wykonanie operacji std :: swap (x, x).

W związku z powyższym, std daje podmiotowi wdrażającemu gwarancję: X musi spełnić MoveAssignable. Nawet jeśli w stanie przeniesionym, klient musi upewnić się, że X jest MoveAssignable. Co więcej, implementacja std::swap nie obchodzi, co robi samo-ruch-przydział, o ile nie jest niezdefiniowanym zachowaniem dla X. o ile nie ulega awarii.

a = std::move(b); 

Gdy & a == & b, zarówno źródło i cel tego zadania ma wartość nieokreśloną przemieszczające (z). Może to być operacja "no-op" lub inna czynność. Dopóki nie ulega awarii, std :: swap będzie działał poprawnie. To dlatego, że w następnym wierszu:

b = std::move(tmp); 

Cokolwiek wartość wszedł a z poprzedniego linia zostanie podana nowa wartość z tmp. I tmp ma oryginalną wartość a. Więc oprócz nagrywania wielu cykli procesora, swap(a, a) jest bez op.

Aktualizacja

latest working draft, N4618 została zmodyfikowana tak, aby jednoznacznie stwierdzić, że w wymogami MoveAssignable wyrażenie:

t = rv 

(gdzie rv jest rvalue) t musi być tylko ekwiwalent wartości rv przed przypisaniem, jeśli t i rv nie odnoszą się do tego samego obiektu. I niezależnie, stan rv jest nieokreślony po przypisaniu. Istnieje dodatkowa uwaga do dalszego wyjaśnienia:

rv musi nadal spełniać wymagania składnika biblioteki, który używa go, czy nie t i rv odnoszą się do tego samego obiektu.

+0

Zgadzam się, że implementator biblioteki kontroluje zarówno std :: swap, jak i 'operator = (T &&)', aby mógł robić to, co chce, dopóki interfejs zewnętrzny jest spełniony, więc w tym sensie sprzeczność jest rozwiązana. Jednak mówisz również, że aby zaimplementować 'std :: swap' w przedstawionym sposobie, należy przyjąć założenia dotyczące implementacji standardowej biblioteki, które nie są gwarantowane przez standard C++ 11, ale to może tylko zrobić bezpiecznie przez realizatora całej biblioteki? Czy możesz powiedzieć, że MoveAssignable sugeruje, że 'v = std :: move (v)' musi działać, a przynajmniej nie ulegać awarii? –

+0

'std :: wektor' nie musi być' MoveAssignable' dla zamiany wektorów, ponieważ 'swap' jest wyspecjalizowany dla' wektorów'. Jednak na pewno powinno być możliwe 'std :: sort' a' vector > 'iw tym przypadku' std :: sort' wymaga, aby 'vector ' był MoveAssignable, niezależnie od tego, czy został przeniesiony z czy nie. Mechanizm 'sort' może sam się przesuwać - przypisać ten wektor' '(lub zrobić wszystko, aby zwolnić algorytm). Jednak nie wierzę, że "sortujący" implementor może założyć, jaka jest wartość wynikająca z samo-przeniesienia-zadania, lub że jest to operacja "no-op". –

+0

Więc ty mówisz, że dla typu, który ma być MoveAssignable, musi on zezwalać na samoczynne przenoszenie, a więc nie może przyjąć nie-aliasingu pomiędzy tym "a" parametrem w operatorze przypisania ruchu? To rozwiązałoby sprzeczność w pytaniu. –

2

zgadzam się z analizą, aw rzeczywistości libstdC++ Debug Mode ma twierdzenie, że będzie ogień na siebie zamiany standardowych kontenerach:

#include <vector> 
#include <utility> 

struct S { 
    std::vector<int> v; 
}; 

int main() 
{ 
    S s; 
    std::swap(s, s); 
} 

typu wrapper S jest potrzebna, ponieważ zamiana wektor bezpośrednio używa specjalizacji, która wywołuje vector::swap() i dlatego nie używa generycznego std::swap, ale S użyje standardowego, a gdy skompiluje się jako C++ 11, który spowoduje automatyczne przypisanie elementu wektora, co spowoduje przerwanie :

/home/toor/gcc/4.8.2/include/c++/4.8.2/debug/vector:159:error: PST. 

Objects involved in the operation: 
sequence "this" @ 0x0x7fffe8fecc00 { 
    type = NSt7__debug6vectorIiSaIiEEE; 
} 
Aborted (core dumped) 

(Nie wiem, co ma znaczyć "PST"! Myślę, że coś jest nie tak z instalacją, z którą ją przetestowałem.)

Uważam, że zachowanie GCC jest zgodne, ponieważ standard mówi, że implementacja może zakładać, że samo-przenoszenie nigdy nie ma miejsca, dlatego twierdzenie nigdy nie zawiedzie w ważnym programie.

Jednak zgadzam się z Howardem, że to musi zadziałać (i można z niego pracować bez większych problemów - dla libstdC++ musimy tylko usunąć asercję trybu debugowania!), Więc musimy naprawić standard zrobić wyjątek dla samoprzeciągania się, a przynajmniej samodmiany. Od pewnego czasu obiecuję napisać artykuł na ten temat, ale jeszcze tego nie zrobiłem.

Sądzę, że od czasu napisania jego odpowiedzi, Howard zgadza się, że jest problem z obecnym brzmieniem normy, i musimy to naprawić, aby zabronić libstdC++ w tworzeniu tego twierdzenia, które zawodzi.