2012-04-06 7 views
16

To pytanie dotyczy specyfikacji kilku funkcji w bibliotece standardowej C++ 11, które przyjmują swoje argumenty jako odniesienia rvalue, ale nie konsumuj je we wszystkich przypadkach. Jednym z przykładów jest std::unordered_set<T>::insert(T&&).std :: unordered_set <T> :: insert (T &&): argument zostanie przeniesiony, jeśli istnieje

Jest całkiem jasne, że ta metoda użyje konstruktora ruchu z T do skonstruowania elementu w kontenerze, jeśli jeszcze nie istnieje. Co się jednak stanie, jeśli element już istnieje w kontenerze? Jestem prawie pewien, że nie ma powodu, aby zmienić przedmiot w tej sprawie. Jednak nie znalazłem nic w standardzie C++ 11 wspierającym moje roszczenie.

Oto przykład pokazujący, dlaczego może to być interesujące. Poniższy kod odczytuje linie ze std :: cin i usuwa pierwsze wystąpienie duplikatów linii.

std::unordered_set<std::string> seen; 
std::string line; 
while (getline(std::cin, line)) { 
    bool inserted = seen.insert(std::move(line)).second; 
    if (!inserted) { 
     /* Is it safe to use line here, i.e. can I assume that the 
     * insert operation hasn't changed the string object, because 
     * the string already exists, so there is no need to consume it. */ 
     std::cout << line << '\n'; 
    } 
} 

Wygląda na to, że ten przykład działa z GCC 4.7. Ale nie jestem pewien, czy jest to zgodne z normą.

+5

Byłoby niewydajne, aby przenieść, gdy nie jest wymagane, ale tylko standard ma znaczenie. Dobre pytanie! –

+0

'std :: cout << linia << '\ n';' Zwróć uwagę, że standard nie mówi, że to zadziała. "linia" pozostanie w "prawidłowym stanie o nieokreślonej wartości". Zamiast tego należy zaznaczyć opcję 'line.empty()'. –

+1

To wszystko brzmi, jakby to był świetny raport o wadzie. –

Odpowiedz

6

znalazłem tę notatkę w normie (17.4.6.9):

[Uwaga: Jeśli program rzuca lwartością do xvalue podczas przechodzenia tej lwartość do funkcji biblioteki (na przykład przez wywołanie funkcja z argumentem move(x)), program skutecznie prosi tę funkcję, aby traktowała tę lwartość jako tymczasową. Wdrożenie może zoptymalizować kontrole aliasingu, które mogą być potrzebne, jeśli argument był l-wartością. - nota końcowa]

Chociaż nie odpowiada bezpośrednio na twoje pytanie, to wskazuje, że skutecznie "podałeś" argument funkcji bibliotecznej jako tymczasowy, więc nie będę polegał na jego wartości, gdy Nazywa się insert. O ile mogę powiedzieć, implementacja biblioteki byłaby uprawniona do przejścia z tego parametru, nawet jeśli później ustali, że nie będzie utrzymywał wartości w kontenerze.

+0

Istnieje jednak coś więcej niż tylko optymalizacja: 'unique_ptr' gwarantuje, że po ruchu oryginalny wskaźnik jest pusty. Czy tak jest w przypadku wstawiania unikalnego wskaźnika do mapy? –

+6

@KerrekSB: Ważne jest, aby być naprawdę jednoznacznym ze znaczeniem * move * w odniesieniu do gwarancji, którą wskazujesz. * * Ruch * nie ma * wywoływania * 'std :: move (uptr)', ale * move-construction * lub * move-assignment *. Oznacza to, że rzutowanie na * rvalue-reference * nie usuwa wskaźnika z 'unique_ptr'. –

0

To się zgadza.

Oczywiście - gdy mamy do czynienia z filozofią - wszystko może być kwestionowane, ale kompilator musi jakoś to zrobić.

Wybór projektantów było to -to wykonać ruch - nie musi być miejscem do iść. Jeśli nie ma takiego miejsca, ruch się nie dzieje.

Należy zauważyć, że niezależnie od funkcji przyjmuje się & &, przyjmuje się, że parametr jest "tymczasowy": jeśli nie "ukradł" danych, tymczasowy zostanie zniszczony na końcu wyrażenia. Jeśli tymczasowość była wymuszona (przez std :: move), obiekt pozostanie tam w każdym przypadku, dopóki nie zostanie zniszczony przez swój własny zakres. Z lub bez oryginalnych danych w środku.

+3

Zapominasz, że biblioteka może przenieść argument do tymczasowego, a następnie przejść z tymczasowego do ostatecznego miejsca docelowego. Nie sądzę, aby liczba ruchów była ograniczona, nawet jeśli implementacja będzie próbowała uniknąć ich w jak największym stopniu ze względu na wydajność. –

+0

@ MatthieuM. Chodzi nie o to, "ile kroków". Chodzi o to, że nie jest w celu pojemnika zniszczenie przychodzących treści. Do wyboru była (i nie była to moja zabawa ...) przeniesienie zawartości, jeśli w pojemniku jest miejsce do umieszczenia. W przeciwnym razie zostaw go tam, gdzie odchodzi. –

1

Semantyka podana dla insert na nieuporządkowanych pojemnikach asocjacyjnych w § 23.2.5/Tabela 103 nie precyzują, czy konstruktor akcja argument insert jest wywoływana, jeśli wkładka nie sformułowanie mówi jedynie o tym, czy wprowadzenie dzieje:

a_uniq.insert(t)

Powroty: pair<iterator, bool>

Wymagania: Jeśli t jest const RValue wyrażenie T wynosi MoveInsertable do X; w przeciwnym razie, T będzie CopyDirectable do X.

Efekty: Wstawki t wtedy i tylko wtedy nie ma elementu w pojemniku z kluczem odpowiednik tonacji t. Składnik bool zwracanej pary wskazuje, czy wstawienie ma miejsce, a komponent iteratora wskazuje element z kluczem równoważnym kluczowi t.

Jednak specyfikacja emplace jest jaśniejszy (również z tabeli 103), i powinieneś być w stanie go używać zamiast insert dostać gwarancje, które chcesz:

a_uniq.emplace(args)

Powroty: pair<iterator, bool>

wymaga: T będzie EmplaceConstructible do X z args.

Efekty: Wstawia T obiekt t zbudowane z std :: przodu (args) ... tylko wtedy, gdy istnieje żaden element w kontenerze z kluczem odpowiednik tonacji t. Składnik boolowy zwracanej pary ma wartość true tylko wtedy, gdy wstawiane jest wstawienie , a komponent iteracyjny pary punktów do elementu z kluczem równoważnym klawiszowi t.

I takiej interpretacji ((Wstawia T obiekt t zbudowany ...) (tylko wtedy, gdy istnieje żaden element w kontenerze ...)), tj zarówno wkładanie i budowa powinna zdarza się to tylko wtedy, gdy w kontenerze nie ma pasującego elementu. Jeśli nie zostanie utworzony żaden obiekt, przekazany obiekt nie zostanie przekazany do konstruktora ruchu, a zatem nadal będzie ważny po niepowodzeniu wywołania emplace.

gcc 4.7.0 nie wydaje się wspierać unordered_set::emplace, ale jest w standardzie (§ 23.5.6.1)

Jak @NicolBolas zauważył w komentarzach, a mimo powyższego, że to niemożliwe, aby wdrożyć emplace funkcję, która nie skonstruowania T jeśli sprzeczne wpis już istnieje.

Więc jedynym sposobem, aby dostać semantykę chcesz w sposób, który jest zgodny ze standardami byłoby zrobić find następnie, warunkowo, insert lub emplace.

+1

'zarówno wstawianie jak i konstrukcja powinny się zdarzać tylko wtedy, gdy nie ma pasującego elementu już w pojemniku': To nie jest możliwe. Musi utworzyć 'T', aby * znaleźć * to. 'T' jest skonstruowany w dowolny sposób. Więc jeśli 'T' jest ruchomy i możesz przekazać' T && ', to * musi * wywołać konstruktor ruchu. –

+0

@NicolBolas dobry punkt ... według mojej interpretacji standard jest niemożliwy do zastosowania. Będę edytować powyższe – je4d

Powiązane problemy