C ++ вызывает std :: move на std :: unique_ptr без копирования - PullRequest
0 голосов
/ 07 ноября 2018

Я генерирую std::unique_ptr<Device> в цикле и добавляю их к std::map<size_t, std::unique_ptr<Device>>, который является переменной-членом.

Заголовок:

#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 при создании пары для вставки в карту. Как я могу избежать этого?

Ответы [ 2 ]

0 голосов
/ 08 ноября 2018

Если вы согласны с последующими вставками с дублирующимися ключами, перезаписывающими предыдущие, то:

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

Если нет, то:

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

Причина, по которой последнее работает, а код в вопросе - нет, состоит в том, что до C ++ 17 метод insert имел дефектный набор перегрузок, который вы можете увидеть здесь :

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

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

Обычно перегрузка # 2 должна обрабатывать случаи, когда необходимо переместить аргумент, но в вашем случае, так как вы решили передать список фигурных скобок init, список P не может быть выведен, и # 1 выигрывает разрешение перегрузки и пытается скопировать аргумент. Используя std::make_pair, вы можете принудительно удержать вывод для # 2, который затем выигрывает и делает правильные вещи.

В C ++ 17 это исправлено, поскольку добавлена ​​перегрузка # 3:

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

, которая выиграет разрешение перегрузки с аргументом braced-init-list.

0 голосов
/ 07 ноября 2018

Вы можете построить его на месте, избегая создания каких-либо временных:

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