std::map<>:: вставка с использованием не копируемых объектов и равномерной инициализации

Посмотрите на следующий код:

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

1 ответ

Решение

[Это полное переписывание. Мой предыдущий ответ не имел ничего общего с проблемой.]

map имеет два соответствующих insert перегрузки:

  • insert(const value_type& value), а также

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

Когда вы используете простой список-инициализатор map.insert({1, non_copyable()});, все возможные перегрузки рассматриваются. Но только первый (тот, который принимает const value_type&), поскольку другой не имеет смысла (нет никакого способа волшебным образом догадаться, что вы хотели создать пару). Первая перегрузка, конечно, не работает, так как ваш элемент не копируется.

Вы можете заставить работать вторую перегрузку, явно создав пару 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 конструкторы, находит соответствующий подвижный, и в результате получается пара значений, которая связывается с P&&перегрузка insert функция.

(Другой вариант заключается в использовании emplace() с piecewise_construct а также forward_as_tupleхотя это было бы намного более многословным.)

Я полагаю, что мораль здесь в том, что инициализаторы списка ищут жизнеспособные перегрузки - но они должны знать, что искать!

Помимо других ответов по предоставлению конструктора перемещения (присваивания), вы также можете хранить не копируемый объект через указатель, особенно unique_ptr, unique_ptr будет обрабатывать движение ресурсов для вас.

Другие вопросы по тегам