将对象从一个unordered_map移动到另一个容器中

15

我的问题是关于安全性的。我查阅了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的迭代器中移动可能会导致迭代器存储的键值被删除,从而使其哈希无效;因此之后对其进行的任何操作都可能导致未定义的行为。除非不是这样?

我认为值得注意的是,在GCC 4.8.2中,上述操作似乎成功地按预期工作,因此我想看看是否错过了支持或明确不支持该行为的文档。

1个回答

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

是的,这很安全,因为它实际上并没有修改键值。这是不可能的,因为键被声明为const。 *it 的类型是 std::pair<const ConstAPtr, int>。 当它被移动时,第一个成员(即const ConstAPtr)实际上并没有移动。它通过 std::move 被转换为r-value,并变成了 const ConstAPtr&&。但是这与移动构造函数不匹配,因为它需要一个非const的ConstAPtr&&。因此,调用了复制构造函数。


那么它实际上会调用std::pair<const ConstAPtr,int>的复制构造函数吗?所以假设第二个模板参数是一个复杂的结构体...在这种情况下,我因此无法从std::move()中获得任何好处?因此,我需要将复杂结构移动到新的pair中? - inetknght
@inetknght:这是我一开始的想法。然而,我现在正在进行测试,似乎表明情况并非如此。当我创建一个 std::pair<const std::string, std::string> 并对其使用 std::move 时,似乎第二个字符串实际上是空的,尽管第一个字符串保持不变,这正如预期的那样。我需要进一步调查这个问题。 - Benjamin Lindley
阅读您的编辑后--所以第一个对象确实被复制了,但第二个对象被移动了。嗯...所以如果第一个对象是一个复杂结构,那可能很糟糕。在使用shared_ptr时,我想知道互斥锁的开销,但这超出了本问题的范围,不知道是否有解决方法。感谢您的帮助! - inetknght

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接