【问题标题】:std::map<>::insert using non-copyable objects and uniform initializationstd::map<>::insert 使用不可复制对象和统一初始化
【发布时间】:2013-02-01 17:07:05
【问题描述】:

看看下面的代码:

#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 上取消注释标记行时,编译此 sn-p 失败。产生的错误表明non_copyable不能被复制,但我预计它会被移动。

为什么插入使用统一初始化构造的std::pair 失败,但插入使用std::make_pair 构造的却失败?两者都不应该产生可以成功移动到地图中的右值吗?

【问题讨论】:

    标签: c++ c++11 noncopyable movable


    【解决方案1】:

    [这是一个完整的重写。我之前的回答与问题无关。]

    map 有两个相关的insert 重载:

    • insert(const value_type&amp; value),和

    • &lt;template typename P&gt; insert(P&amp;&amp; value).

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

    您可以通过显式创建该对来使第二个重载工作,或者使用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 构造函数,找到相关的可移动构造函数,结果是一个右值对,它绑定到insert 函数的P&amp;&amp;-overload。

    (另一种选择是将emplace()piecewise_constructforward_as_tuple 一起使用,尽管这样会更冗长。)

    我想这里的寓意是列表初始化器寻找可行的重载——但他们必须知道要寻找什么!

    【讨论】:

    • 是的,这基本上是我在删除答案之前写的。不过我有一个疑问:为什么在这里创建initializer_list&lt;&gt;std::pair&lt;int, non_copyable&gt; 似乎没有一个可以接受的构造函数。我认为统一的初始化语法只会选择 pair&lt;&gt; 的常规构造函数。
    • 另外,initializer_list 元素必须是同一类型,不是吗?
    • +1 到两个 cmets。在我的示例中,甚至不应该有 initializer_list。这更像是使用统一初始化调用std::pair 的构造函数。
    • 我想令人惊讶的是map 确实 value_type &amp;&amp; 有一个 insert 重载,而 vector 有。
    • @KerrekSB C++17 加了一个,顺便说一句
    【解决方案2】:

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

    【讨论】:

    • 这是一个令人困惑的答案。 unique_ptr 不会处理对象的资源移动,而是您只存储指向对象本身的指针,因此不会移动对象的资源 - 仅移动 unique_ptr 本身。
    猜你喜欢
    • 2019-05-17
    • 2018-05-07
    • 2022-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-27
    • 1970-01-01
    相关资源
    最近更新 更多