使用不可复制的对象和统一初始化方式进行std::map<>::insert操作

17

请查看以下代码:

#include <utility>
#include <map>

// non-copyable but movable
struct non_copyable {
    non_copyable() = default;

    non_copyable(non_copyable&&) = default;
    non_copyable& operator=(non_copyable&&) = default;

    // you shall not copy
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

int main() {
    std::map<int, non_copyable> map;
    //map.insert({ 1, non_copyable() });  < FAILS
    map.insert(std::make_pair(1, non_copyable()));
    // ^ same and works
}

当在g++ 4.7取消注释标记行时,编译此片段将失败。产生的错误表明non_copyable无法复制,但我预期它会被移动。

为什么使用统一初始化构造的std::pair插入失败,而使用std::make_pair构造的却没有?难道两者都应该生成可以成功移动到映射中的rvalue吗?

2个回答

22

[这是完全重写。我的早期答案与问题无关。]

map有两个相关的insert重载:

  • insert(const value_type& value),和

  • <template typename P> insert(P&& value)

当你使用简单的列表初始化器map.insert({1, non_copyable()});时,所有可能的重载都会被考虑。但是只有第一个(接受const value_type&的那个)被发现,因为另一个没有意义(没有办法神奇地猜测你想创建一对)。当然,第一个重载不起作用,因为你的元素是不可复制的。

你可以通过显式创建pair使第二个重载生效,可以使用make_pair,就像你已经描述的那样,或者通过显式命名值类型:

typedef std::map<int, non_copyable> map_type;

map_type m;
m.insert(map_type::value_type({1, non_copyable()}));

现在的列表初始化器知道要查找map_type::value_type构造函数,找到相关的可移动构造函数,结果是一个rvalue pair,它绑定到insert函数的P&&重载。

(另一个选择是使用piecewise_constructforward_as_tupleemplace(),但那会变得更冗长。)

我想这里的教训是,列表初始化器要寻找可行的重载 - 但它们必须知道要查找什么!


1
是的,基本上这就是我在删除答案之前所写的。不过我有一个疑问:为什么要在这里创建一个initializer_list<>std::pair<int, non_copyable>似乎没有一个接受它的构造函数。我认为统一初始化语法只会选择pair<>的常规构造函数。 - Andy Prowl
1
此外,initializer_list<>元素必须全部为相同类型,是吗? - eladidan
7
令人惊讶的是,map 没有针对 value_type &&insert 重载,而 vector 却有。 - Kerrek SB
1
@KerrekSB,顺便说一下,C++17增加了一个。 - n.caillou
@n.caillou:谢谢,来自未来四年的人 :-) - Kerrek SB
显示剩余4条评论

0
除了提供移动(赋值)构造函数的其他答案之外,您还可以通过指针存储不可复制的对象,特别是使用unique_ptrunique_ptr将为您处理资源移动。

这是一个令人困惑的答案。unique_ptr不会处理对象的资源移动,而只是存储对象本身的指针,因此对象的任何资源都不会被移动 - 只有unique_ptr本身会被移动。 - Petr

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