我正在玩Move Semantics和[r | l]值引用,以学习如何在真实世界的程序中使用它们。请考虑以下代码:
// 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&&
的地址并将其存储在指针中。然后将数据移至std::forward_list
(items
)。我假设调用移动赋值不会影响对象的地址。那是对的吗?虽然移动后d
的内容不再有效。这是查找表(lookup
)的内容不正确。
我假设我必须重新订购a)添加查找项目和b)移动实际数据。上面的代码并不理智。这是对的吗?
此外,我不明白为什么我必须在那里说std::move
。编译器应该知道d
是一个右值引用。所以它应该调用std::forward_list<T>::push_front(T&&)
并移动赋值...
答案 0 :(得分:5)
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
。
答案 1 :(得分:1)
我会删除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)}); }