Czy w C ++ można ukraść zasoby z mapy, której już nie potrzebuję? Mówiąc dokładniej, załóżmy, że mam klucze std::map
z std::string
i chcę z niego zbudować wektor, kradnąc zasoby map
kluczy s za pomocą std::move
. Zauważ, że taki dostęp do zapisu do kluczy psuje wewnętrzną strukturę danych (porządkowanie kluczy), map
ale nie będę jej później używał.
Pytanie : Czy mogę to zrobić bez żadnych problemów, czy doprowadzi to do nieoczekiwanych błędów, na przykład w destruktorze, map
ponieważ uzyskałem do niego dostęp w sposób, który std::map
nie był przeznaczony?
Oto przykładowy program:
#include<map>
#include<string>
#include<vector>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
std::vector<std::pair<std::string,double>> v;
{ // new scope to make clear that m is not needed
// after the resources were stolen
std::map<std::string,double> m;
m["aLongString"]=1.0;
m["anotherLongString"]=2.0;
//
// now steal resources
for (auto &p : m) {
// according to my IDE, p has type
// std::pair<const class std::__cxx11::basic_string<char>, double>&
cout<<"key before stealing: "<<p.first<<endl;
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
cout<<"key after stealing: "<<p.first<<endl;
}
}
// now use v
return 0;
}
Daje to wynik:
key before stealing: aLongString
key after stealing:
key before stealing: anotherLongString
key after stealing:
EDYCJA: Chciałbym to zrobić dla całej zawartości dużej mapy i zapisać dynamiczne przydziały dzięki kradzieży zasobów.
c++
move-semantics
Phinz
źródło
źródło
const
wartości jest zawsze UB.std::string
ma krótką optymalizację. Oznacza to, że istnieje pewna nietrywialna logika przy kopiowaniu i przenoszeniu, a nie tylko wymiana wskaźnika, a ponadto większość ruchu polega na kopiowaniu - aby nie radzić sobie z dość długimi łańcuchami. Różnica statystyczna i tak była niewielka i na ogół różni się w zależności od rodzaju wykonywanego przetwarzania łańcucha.Odpowiedzi:
Robisz niezdefiniowane zachowanie, używając
const_cast
do modyfikowaniaconst
zmiennej. Nie rób tego Powodem jestconst
to, że mapy są sortowane według ich kluczy. Tak więc modyfikacja kluczowego miejsca narusza podstawowe założenie, na którym zbudowana jest mapa.Nigdy nie należy używać
const_cast
do usuwaniaconst
ze zmiennej i modyfikowania tej zmiennej.Mając na uwadze powyższe, C ++ 17 ma rozwiązanie problemu:
std::map
„sextract
Funkcja:I nie rób tego
using namespace std;
. :)źródło
map
nie będę narzekał, jeśli nie wywołam jego metod (czego nie robię), a może wewnętrzne porządkowanie nie jest ważne w jego destruktorze?const
. Mutowanieconst
obiektu jest natychmiastowym UB, niezależnie od tego, czy cokolwiek faktycznie uzyskuje do niego dostęp później.extract
podczas używania iteratorów jako argumentu zamortyzowano stałą złożoność. Niektóre koszty ogólne są nieuniknione, ale prawdopodobnie nie będą na tyle znaczące, aby miały znaczenie. Jeśli masz specjalne wymagania nieobjęte tym, musisz wdrożyć własnemap
spełniające te wymagania. Testd
pojemniki są przeznaczone do wspólnego celu ogólnego application.They nie są zoptymalizowane pod kątem konkretnych zastosowań.const
? W takim przypadku proszę wskazać, gdzie moja argumentacja jest błędna.Twój kod próbuje modyfikować
const
obiekty, więc ma niezdefiniowane zachowanie, jak słusznie wskazuje odpowiedź druckermanly .Niektóre inne odpowiedzi ( Phinza i Deuchiego ) argumentują, że klucz nie może być przechowywany jako
const
obiekt, ponieważ uchwyt węzła wynikający z wyodrębnienia węzłów z mapy pozwala na brakconst
dostępu do klucza. Takie wnioskowanie może początkowo wydawać się wiarygodne, ale P0083R3 , dokument wprowadzającyextract
funkcje), ma dedykowaną sekcję na ten temat, która unieważnia ten argument:(moje podkreślenie)
źródło
Nie sądzę, że
const_cast
modyfikacja prowadzi do niezdefiniowanego zachowania w tym przypadku, ale proszę o komentarz, czy ta argumentacja jest poprawna.Ta odpowiedź twierdzi, że
Zatem wiersz
v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
w pytaniu nie prowadzi do UB wtedy i tylko wtedy, gdystring
obiektp.first
nie został utworzony jako obiekt stały. Zauważ teraz, że odniesienie doextract
stanówWięc jeśli odpowiadający
extract
node_handle
p
,p
nadal żyje w jego miejscu przechowywania. Ale po ekstrakcji wolno mi usunąćmove
zasobyp
jak w kodzie odpowiedzi druckermanly'ego . Oznacza to, żep
i dlatego teżstring
obiekt niep.first
został pierwotnie utworzony jako obiekt stały.Dlatego myślę, że modyfikacja
map
kluczy nie prowadzi do UB i z odpowiedzi Deuchie wydaje się, że również teraz uszkodzona struktura drzewa (obecnie wiele takich samych pustych kluczy łańcuchowych)map
nie wprowadza problemów w destruktorze. Tak więc kod w pytaniu powinien działać poprawnie przynajmniej w C ++ 17, w którymextract
istnieje metoda (a oświadczenie o wskaźnikach pozostało ważne).Aktualizacja
Jestem teraz zdania, że ta odpowiedź jest błędna. Nie usuwam go, ponieważ odwołują się do niego inne odpowiedzi.
źródło
std::launder
.EDYCJA: Ta odpowiedź jest błędna. W miłych komentarzach wskazano na błędy, ale nie usuwam ich, ponieważ przywołano je w innych odpowiedziach.
@druckermanly odpowiedział na twoje pierwsze pytanie, które mówiło, że
map
wymuszona zmiana kluczy psuje porządek, na którymmap
zbudowana jest wewnętrzna struktura danych (drzewo czerwono-czarne). Ale korzystanie z tejextract
metody jest bezpieczne, ponieważ robi dwie rzeczy: wyjmij klucz z mapy, a następnie usuń go, aby w ogóle nie wpływał na porządek mapy.Drugie pytanie, które zadałeś, czy spowodowałoby kłopoty podczas dekonstrukcji, nie stanowi problemu. Kiedy mapa dekonstruuje, wywoła dekonstruktor każdego z jej elementów (map_types itp.), A
move
metoda zapewnia bezpieczeństwo dekonstrukcji klasy po jej przeniesieniu. Więc nie martw się. Krótko mówiąc, jest to działaniemove
zapewniające bezpieczne usunięcie lub ponowne przypisanie nowej wartości do klasy „przeniesionej”. W szczególnościstring
,move
metoda może ustawić swój wskaźnik char nanullptr
, więc nie usuwa rzeczywistych danych, które zostały przeniesione, gdy wywołano dekonstruktor oryginalnej klasy.Komentarz przypomniał mi o punkcie, który przeoczyłem, w zasadzie miał rację, ale jest jedna rzecz, z którą całkowicie się nie zgadzam:
const_cast
prawdopodobnie nie jest to UB.const
to tylko obietnica między kompilatorem a nami. obiekty oznaczone jakoconst
nadal są obiektami, takimi samymi jak te, które nie sąconst
, pod względem ich typów i reprezentacji w postaci binarnej. Gdyconst
zostanie odrzucony, powinien zachowywać się tak, jakby był normalną klasą podlegającą modyfikacji. Jeśli chodzi omove
: Jeśli chcesz go użyć, musisz przekazać&
zamiast zamiastconst &
, więc, jak widzę, nie jest to UB, po prostu łamie obietnicęconst
i przenosi dane.Przeprowadziłem również dwa eksperymenty, używając odpowiednio MSVC 14.24.28314 i Clang 9.0.0, i dały one ten sam wynik.
wynik:
Widzimy, że ciąg „2” jest teraz pusty, ale oczywiście złamaliśmy porządek mapy, ponieważ pusty ciąg powinien zostać ponownie umieszczony z przodu. Jeśli spróbujemy wstawić, znaleźć lub usunąć niektóre określone węzły mapy, może to spowodować katastrofę.
W każdym razie możemy zgodzić się, że nigdy nie jest dobrym pomysłem manipulowanie wewnętrznymi danymi jakichkolwiek klas z pominięciem ich publicznych interfejsów.
find
,insert
,remove
Funkcje i tak dalej polegać ich poprawności na uporządkowanie wewnętrznej struktury danych, i to jest powód, dlaczego powinniśmy się trzymać z dala od myśli o wgląd wnętrza.źródło
const
wartości) miało miejsce wcześniej. Jednak argument „move
[funkcja] zapewnia, że można bezpiecznie zdekonstruować [obiekt] klasy po jej przeniesieniu” nie ma zastosowania: nie można bezpiecznie przejść zconst
obiektu / odwołania, ponieważ wymaga to modyfikacji, któraconst
zapobiega. Możesz spróbować obejść to ograniczenie, używającconst_cast
, ale w tym momencie w najlepszym razie wchodzisz głęboko w zachowanie specyficzne dla implementacji, jeśli nie UB.move
funkcja przyjmuje&
zamiast aconst&
, więc jeśli ktoś nalega, aby wyprowadził klucz z mapy, musi go użyćconst_cast
.const_cast
będzie nieznośne. Może to działać przez większość czasu, ale łam kod w subtelny sposób.extract
możemy przesuwać się z tego samego obiektu, prawda? (patrz moja odpowiedź)std::launder