Подтвердить что ты не робот

Перемещение объектов из одного unordered_map в другой контейнер

Мой вопрос заключается в безопасности. Я искал cplusplus.com и cppreference.com, и им, похоже, не хватает безопасности итератора во время std:: move. В частности: безопасно ли вызывать std:: unordered_map:: erase (iterator) с итератором, объект которого перемещен? Пример кода:

#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;
}

Причина, по которой я спрашиваю, это то, что мне кажется, что перемещение пары из ирантера unordered_map может (?), следовательно, * re * переместить значение хранимого ключа итератора и, следовательно, аннулировать его хеш; поэтому любые последующие операции могут привести к поведению undefined. Разве что не так?

Мне кажется, стоит отметить, что вышеприведенное, похоже, успешно работает так, как это предусмотрено в GCC 4.8.2, поэтому я хочу посмотреть, не пропустил ли я документацию, поддерживающую или явно не поддерживающую поведение.

4b9b3361

Ответ 1

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

Да, это безопасно, потому что это фактически не изменяет ключ. Он не может, потому что ключ const. Тип *it равен std::pair<const ConstAPtr, int>. Когда он перемещается, первый элемент (const ConstAPtr) фактически не перемещается. Он преобразуется в r-значение на std::move и становится const ConstAPtr&&. Но это не соответствует конструктору перемещения, который ожидает не const const ConstAPtr&&. Поэтому вместо этого вызывается конструктор копирования.