【问题标题】:Is getting address of rvalue reference before move considered safe?在移动之前获取右值引用的地址是否安全?
【发布时间】:2013-08-07 20:32:29
【问题描述】:

我正在使用 Move Semantics 和 [r|l]value 引用来学习如何在实际程序中使用它们。考虑以下代码:

// Item is a heavy class having move ctor and assignment but no copy. 
std::map<std::string, Item*> lookup;
std::forward_list<Item> items;
void FooClass::addItem(Item&& d) {
    if (lookup.find(d.getName()) == lookup.end()) {
        lookup[d.getName()] = &d;    //<== not safe after move?
        items.push_front(std::move(d));
    }
}

我正在获取Item&amp;&amp; 的地址并将其存储在指针中。然后将数据移至std::forward_list (items)。我假设调用移动赋值不会影响对象的地址。那是对的吗?虽然d 的内容在移动后不再有效。那就是查找表(lookup)的内容不正确。

我假设我必须重新排序 a) 添加查找项和 b) 移动实际数据。上面的代码不理智。这是正确的吗?

我也不明白为什么我必须在那里说std::move。编译器应该知道d 是一个右值引用。所以它应该调用std::forward_list&lt;T&gt;::push_front(T&amp;&amp;) 并移动分配...

【问题讨论】:

    标签: c++ c++11 move-semantics rvalue-reference


    【解决方案1】:
        lookup.[d.getName()] = &d;    //<== not safe after move?
    

    这是完全不安全的,但不仅仅是因为搬家。与您的问题标题相反,您不是在获取右值引用的地址,而是在获取左值的地址,而是在函数返回后不久可能会超出范围的地址,这将留下一个悬空指针。考虑:

    FooClass f;
    f.addItem( Item() );
    

    这会将临时地址添加到映射中,因此如果您取消引用该指针,您的程序具有未定义的行为,这就是不安全的缩影。

    下一行的移动可能会使事情变得更糟,因为映射中指针所指的对象可能会被移动修改,从而在映射中留下指向移动源Item 的指针,但这没什么与函数返回后超出范围所导致的未定义行为相比。

    使代码安全是微不足道的,因此没有理由按照您的方式编写它。

        items.push_front(std::move(d));
        auto& item = items.front();
        lookup[item.getName()] = &item;
    

    现在映射中的指针指向一个不会超出范围的对象。只要元素在forward_list 中,指针就有效。

    【讨论】:

    • 鉴于 pos == lookup.end(),提供它作为提示真的有帮助吗?
    • @Nevin,哎呀,我的意思是使用lower_bound 并检查是否找到了元素,但忘记完成那一点答案!我只是删除它,现在懒得显示正确的形式;)
    • 我认为lookup[d.getName()] = &amp;items.front() 也不理智 ;)
    • @soroush,为什么?正如我所说,在您从 items 中删除该元素之前,该指针将一直有效,这保证在函数返回之后并且在您的控制之下,而 d 可能被销毁函数返回后,不一定在你的控制范围内。
    • @soroush:运动不是这样的。所有动作都是根据给定值是右值还是左值引用来调用不同的构造函数/赋值运算符。这是导致复制或移动的复制/移动构造函数和分配的差异。运动不会破坏现有对象或“转移”它或类似的东西。它只允许对给定对象进行非常量更改,例如窃取资源等。对象移出后仍然存在
    【解决方案2】:

    我会摆脱 items 并更改 lookup 以按值存储 Item,如下所示:

    using Lookup = std::map<std::string, Item>;
    Lookup lookup;
    
    void addItem(Item&& d)
    { lookup.insert(std::pair<std::string const&, Item&&>{d.getName(), std::move(d)}); }
    

    【讨论】:

    • 使用std::make_pair(d.getName(), std::move(d)) 而不是手动指定对的完整类型!或者,至少,使用Lookup::value_type{d.getName(), std::move(d)}
    • 或者更好的是,使用emplace(d.getName(), std::move(d)),这样打字更少,效率更高
    • 我使用 pair 因为我想要一对引用;我不想产生复制/移动成本,除非该元素实际上要插入到地图中。我不相信 Lookup::value_type (这是我最初的解决方案)或 emplace 会延迟,对吗?
    猜你喜欢
    • 2014-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 2021-12-04
    • 2015-10-18
    相关资源
    最近更新 更多