C++ 在 std::unique_ptr 上调用 std::move 而不复制

C++ calling std::move on std::unique_ptr without copying

我在一个循环中生成一个 std::unique_ptr<Device> 并将它们添加到一个 std::map<size_t, std::unique_ptr<Device>> 这是一个成员变量。

Header:

#include "util.h"
#include <map>
#include <memory>

class Container {
  public:
    explicit Container(char* path);

  private:
    std::map<size_t, std::unique_ptr<Device>> devices_;
}

实施:

Container::Container(char* path) {
  std::vector<char*> files = util::list_files(path);
  for(size_t i = 0; i < files.size(); i++) {
    auto device = util::CreateDevice(file); // Returns std::unique_ptr<Device>
    devices_.insert({i, std::move(device)});
  }
}

util.h 负责创建 unique_ptr<Device>,我没有它的来源,所以我无法更改它。插入地图时,我需要调用 std::move,否则一旦超出范围(例如下一次迭代)就会破坏。

此实现构建良好,适用于 Visual Studio 2017 编译器。但是,在 Travis-CI 上使用 clang,我收到以下错误:

/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/ext/new_allocator.h:120:23: error: 
      call to implicitly-deleted copy constructor of 'std::pair<const unsigned
      long, std::unique_ptr<Device
      std::default_delete<Device> > >'
        { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
                             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~

我的实现似乎在创建要插入地图的对时试图复制 unique_ptr。我怎样才能避免这种情况?

您可以就地构建它,避免创建任何临时对象:

devices_.emplace
(
   ::std::piecewise_construct
,  ::std::forward_as_tuple(i)
,  ::std::forward_as_tuple(::std::move(device))
);

如果您可以接受后续插入的重复键覆盖之前的键,那么:

devices_[i] = std::move(device);

如果没有,则:

devices_.insert(std::make_pair(i, std::move(device)));

后者起作用而问题中的代码不起作用的原因是在 C++17 之前,insert 方法有一组有缺陷的重载,您可以看到 here:

std::pair<iterator, bool> insert( const value_type& value ); // 1

template<class P>
std::pair<iterator,bool> insert( P&& value ); // 2

通常,重载 #2 应该处理需要从中移出参数的情况,但在您的情况下,由于您选择传递括号初始化列表,P 不能被推导,#1 赢得重载决议并尝试复制参数。通过使用 std::make_pair,您可以强制推导 #2 成功,然后它获胜并做正确的事情。

在 C++17 中,这是固定的,因为添加了重载 #3:

std::pair<iterator,bool> insert( value_type&& value );

这将通过 braced-init-list 参数赢得重载决议。