Ponownie używasz przeniesionego kontenera?

84

Jaki jest prawidłowy sposób ponownego wykorzystania przeniesionego kontenera?

std::vector<int> container;
container.push_back(1);
auto container2 = std::move(container);

// ver1: Do nothing
//container2.clear(); // ver2: "Reset"
container = std::vector<int>() // ver3: Reinitialize

container.push_back(2);
assert(container.size() == 1 && container.front() == 2);

Z tego, co przeczytałem w wersji roboczej standardu C ++ 0x; ver3 wydaje się być poprawnym sposobem, ponieważ obiekt po przeniesieniu jest w formacie

„O ile nie określono inaczej, takie przeniesione przedmioty powinny być umieszczone w stanie ważnym, ale nieokreślonym”.

Nigdy nie znalazłem żadnego przypadku, w którym jest to „określone inaczej”.

Chociaż uważam, że ver3 jest nieco okrężna i miałbym znacznie preferowaną wersję ver1, chociaż vec3 może pozwolić na dodatkową optymalizację, ale z drugiej strony może łatwo prowadzić do błędów.

Czy moje założenie jest prawidłowe?

ronag
źródło
4
Możesz po prostu zadzwonić clear, ponieważ nie ma warunków wstępnych (a zatem nie ma zależności od stanu obiektu).
Nicol Bolas
@Nicol: Powiedzmy, że była std::vectorimplementacja, która przechowała wskaźnik do swojego rozmiaru (wydaje się głupi, ale legalny). Przejście z tego wektora może pozostawić wskaźnik NULL, po czym clearzakończy się niepowodzeniem. operator=może również zawieść.
Ben Voigt,
9
@Ben: Myślę, że naruszałoby to „ważną” część „prawidłowego, ale nieokreślonego”.
ildjarn
1
@ildjarn: Myślałem, że oznacza to po prostu, że można bezpiecznie uruchomić destruktor.
Ben Voigt,
Myślę, że pytanie brzmi: co jest „ważne”?
ronag

Odpowiedzi:

97

Z sekcji 17.3.26 specyfikacji „stan ważny, ale nieokreślony”:

stan obiektu, który nie jest określony, z wyjątkiem tego, że niezmienniki obiektu są spełnione, a operacje na obiekcie zachowują się tak, jak określono dla jego typu [Przykład: Jeśli obiekt xtypu std::vector<int>jest w prawidłowym, ale nieokreślonym stanie, x.empty()można go wywołać bezwarunkowo i x.front()można go wywołać tylko jeśli x.empty()zwraca fałsz. - koniec przykładu]

Dlatego obiekt jest pod napięciem. Możesz wykonać dowolną operację, która nie wymaga warunku wstępnego (chyba że najpierw zweryfikujesz warunek wstępny).

clearna przykład nie ma żadnych warunków wstępnych. I przywróci obiekt do znanego stanu. Po prostu wyczyść go i używaj jak zwykle.

Nicol Bolas
źródło
Gdzie w standardzie mogę poczytać o "warunkach wstępnych" np. Dla metod std :: vector?
ronag
1
@ronag: §23.2 zawiera tabele, w których są one wymienione.
Grizzly
2
Znalazłem następującą informację, która jest interesująca, open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3241.html , piszą „kontenery mogą być„ bardziej puste niż puste ””.
ronag
4
@ronag: 1) Jeśli kontener jest w prawidłowym stanie, wywołanie clearjest prawidłowe. 2) Gdy kontener znajdował się w nieokreślonym stanie, wywołanie clearustawia kontener w określonym stanie, ponieważ w standardzie obowiązują warunki końcowe (§ 23.2.3, tabela 100). std::vector<T>ma niezmiennik klasy, który push_back()jest zawsze ważny (tak długo, jak Tjest CopyInsertable).
ildjarn
3
@ronag: open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3241.html cytuje jeden z komentarzy organu krajowego na temat cytatu „bardziej pusty niż pusty”. Komentarz organu krajowego był nieprawidłowy. N3241 nie proponował takiego stanu. Jeśli implementacja std :: container ma stan „pusty niż pusty” wynikający z przeniesienia z, wówczas ten stan musi być prawidłowym stanem (tj. Możesz zrobić wszystko z tym obiektem, co nie wymaga żadnych warunków wstępnych).
Howard Hinnant,
11

Obiekt jest w prawidłowym, ale niezdefiniowanym stanie zasadniczo oznacza, że ​​chociaż dokładny stan obiektu nie jest gwarantowany, jest prawidłowy i jako takie funkcje składowe (lub funkcje niebędące funkcjami) gwarantują działanie, o ile nie polegają na na obiekcie o określonym stanie.

Funkcja clear()członkowska nie ma żadnych warunków wstępnych dotyczących stanu obiektu (poza tym, że jest poprawna, oczywiście) i dlatego może być wywoływana na obiektach przeniesionych. Z drugiej strony front()zależy na przykład od tego, czy pojemnik nie jest pusty, a zatem nie można go wywołać, ponieważ nie ma gwarancji, że nie będzie pusty.

Dlatego zarówno wersja 2, jak i wersja 3 powinny być w porządku.

Siwy
źródło
Wektor zawsze będzie pusty, ale nie dotyczy to przypadku ogólnego, (tablica IE)
Mooing Duck,
„Wektor zawsze będzie pusty”, na czym to opierasz?
ronag
1
@ronag: Miałem na myśli oczywiście wersje 2 i 3 (jak powinno wynikać z tekstu, naprawiłem tę literówkę
Grizzly
Co ciekawe, warunki wstępne front()podano tylko dla std::array, a nawet ich nie ma w tabeli.
Ben Voigt,
1
@Ben: §23.2.3 tabela 100 mówi, że semantyka operacyjna front()are *a.begin(), §23.2.1 / 6 mówi „ Jeśli kontener jest pusty, tobegin() == end() ”, a § 24.2.1 / 5 mówi „ Biblioteka nigdy nie zakłada, że ​​przeszłość- wartości końcowe można usunąć. ”. W związku z tym myślę, że front()można wywnioskować warunki wstępne , choć z pewnością można by to wyjaśnić.
ildjarn
-8

Nie sądzę, że możesz zrobić WSZYSTKO z przeniesionym obiektem (poza zniszczeniem go).

Czy nie możesz użyć swapzamiast tego, aby uzyskać wszystkie zalety przenoszenia, ale pozostawić pojemnik w znanym stanie?

Ben Voigt
źródło
+1. zamiana jest dobrym pomysłem, chociaż nie zadziała we wszystkich przypadkach, np. użycie auto nie zadziała. Może safe_move, które wewnętrznie używa zamiany, mogłoby być pomysłem?
ronag
5
Jest to obiekt na żywo i możesz używać dowolnych funkcji, które nie mają warunków wstępnych (poza niezmiennikami)
Mooing Duck,
Podstawowy szablon dla std::swapma 2 przypisania przenoszenia, z wartościami docelowymi tych przydziałów. To liczy się dla mnie jako „zrobienie czegoś z przeniesionym obiektem”
Caleth,