2014-06-09 23 views
12

Moje pytanie dotyczy bezpieczeństwa. Szukałem cplusplus.com i cppreference.com i wydaje się, że brakuje im bezpieczeństwa iteratora podczas std :: move. W szczególności: czy można bezpiecznie wywoływać std :: unordered_map :: erase (iterator) z iteratorem, którego obiekt został przeniesiony? Przykładowy kod: (?)Przenoszenie obiektów z jednego unordered_map do innego kontenera

#include <unordered_map> 
#include <string> 
#include <vector> 
#include <iostream> 
#include <memory> 

class A { 
public: 
    A() : name("default ctored"), value(-1) {} 
    A(const std::string& name, int value) : name(name), value(value) { } 
    std::string name; 
    int value; 
}; 
typedef std::shared_ptr<const A> ConstAPtr; 

int main(int argc, char **argv) { 
    // containers keyed by shared_ptr are keyed by the raw pointer address 
    std::unordered_map<ConstAPtr, int> valued_objects; 

    for (int i = 0; i < 10; ++i) { 
     // creates 5 objects named "name 0", and 5 named "name 1" 
     std::string name("name "); 
     name += std::to_string(i % 2); 

     valued_objects[std::make_shared<A>(std::move(name), i)] = i * 5; 
    } 

    // Later somewhere else we need to transform the map to be keyed differently 
    // while retaining the values for each object 

    typedef std::pair<ConstAPtr, int> ObjValue; 

    std::unordered_map<std::string, std::vector<ObjValue> > named_objects; 

    std::cout << "moving..." << std::endl; 

    // No increment since we're using .erase() and don't want to skip objects. 
    for (auto it = valued_objects.begin(); it != valued_objects.end();) { 
     std::cout << it->first->name << "\t" << it->first.value << "\t" << it->second << std::endl; 

     // Get named_vec. 
     std::vector<ObjValue>& v = named_objects[it->first->name]; 
     // move object :: IS THIS SAFE?? 
     v.push_back(std::move(*it)); 

     // And then... is this also safe??? 
     it = valued_objects.erase(it); 
    } 

    std::cout << "checking... " << named_objects.size() << std::endl; 
    for (auto it = named_objects.begin(); it != named_objects.end(); ++it) { 
     std::cout << it->first << " (" << it->second.size() << ")" << std::endl; 
     for (auto pair : it->second) { 
      std::cout << "\t" << pair.first->name << "\t" << pair.first->value << "\t" << pair.second << std::endl; 
     } 
    } 

    std::cout << "double check... " << valued_objects.size() << std::endl; 
    for (auto it : valued_objects) { 
     std::cout << it.first->name << " (" << it.second << ")" << std::endl; 
    } 

    return 0; 
} 

Pytam jest to, że uderza mnie, że przesunięcie pary z iteracyjnej w unordered_map za może zatem * re * przenieść zapisaną wartość klucza iteracyjnej i tym samym unieważnić jego hash; dlatego jakiekolwiek operacje na nim później mogą spowodować niezdefiniowane zachowanie. Chyba że tak nie jest?

Wydaje mi się, że warto zauważyć, że powyższe działanie przebiega zgodnie z zamierzeniami w GCC 4.8.2, dlatego staram się sprawdzić, czy pominięto dokumentację lub wyraźnie nie wspierałem zachowania.

Odpowiedz

5
// move object :: IS THIS SAFE?? 
v.push_back(std::move(*it)); 

Tak, jest bezpieczny, ponieważ tak naprawdę nie modyfikuje klucza. Nie może, ponieważ klucz jest stały. Typ *it to std::pair<const ConstAPtr, int>. Kiedy zostanie przeniesiony, pierwszy element (const ConstAPtr) nie zostanie faktycznie przeniesiony. Jest konwertowany na wartość r przez std::move i staje się const ConstAPtr&&. Ale to nie pasuje do konstruktora ruchu, który spodziewa się, że nie jest stały. Zamiast tego wywoływany jest konstruktor kopii.

+0

I tak faktycznie wywołuje ctor kopii dla std :: pair ? Więc powiedzmy, że drugi parametr szablonu jest złożoną strukturą ... W związku z tym nie uzyskałbym nic ze std :: move()? Chciałbym zatem przenieść złożoną strukturę do nowej pary? – inetknght

+0

@inetknght: Tak myślałem na początku. Jednak teraz robię testy, które zdają się wskazywać, że tak nie jest. Kiedy tworzę 'std :: pair ' i użyję 'std :: move' na nim, wydaje się, że drugi ciąg jest w rzeczywistości pusty, chociaż pierwszy pozostaje nietknięty spodziewany. Będę musiał to dokładniej zbadać. –

+0

Po przeczytaniu wprowadzonych zmian - pierwszy obiekt zostanie skopiowany, a drugi zostanie przeniesiony. Hmmm ... więc jeśli pierwszy obiekt byłby złożoną strukturą, to może być strasznie. Korzystając z shared_ptr, zastanawiam się nad kosztem muteksa w tym, ale to wykracza poza zakres tego pytania i czy istnieje sposób obejścia tego. Dzięki za pomoc! – inetknght