вектор > не компилируется с MSVC - PullRequest
4 голосов
/ 02 мая 2020

Создание вектора карты только для перемещения типов, кажется, не работает должным образом на Windows. См. Код здесь: https://godbolt.org/z/yAHmzh

#include <vector>
#include <map>
#include <memory>

// vector<vector<move-only>> works
void foo() {
    std::vector<std::vector<std::unique_ptr<int>>> outer;
    std::vector<std::unique_ptr<int>> inner;
    std::unique_ptr<int> p = std::make_unique<int>(1);
    inner.push_back(std::move(p));
    outer.push_back(std::move(inner));
}

// vector<map<move-only>> fails to compile upon inserting an element.
void bar() {
    std::vector<std::map<std::unique_ptr<int>, std::unique_ptr<int>>> vec;
    std::map<std::unique_ptr<int>, std::unique_ptr<int>> map;
    std::unique_ptr<int> p1 = std::make_unique<int>(1);
    std::unique_ptr<int> p2 = std::make_unique<int>(2);

    map.insert(std::make_pair(std::move(p1), std::move(p2)));

    // The following line fails to compile on windows. It errors with a message about
    // the unique_ptr copy constructor being explicitly deleted. This seems to only happen
    // on windows. GCC and clang have no problem with this.
    vec.push_back(std::move(map));
}

int main(int argv, char** argc)
{
    foo();

    bar();
}

G CC и Clang не имеют проблем с этим кодом, но MSV C не удается скомпилировать.

В поисках обходного пути, чтобы позволить мне сделать это, который будет компилироваться на всех основных компиляторах.

Ответы [ 2 ]

3 голосов
/ 02 мая 2020

Конструктор перемещения std::map не определен Стандартом как noexcept. Поэтому std::vector возвращается к использованию конструктора копирования (например, с использованием std::move_if_noexcept).

При этом компиляторам разрешено помечать функции как noexcept независимо от того, предписывает это Стандарт или нет. Это, вероятно, то, что делают G CC и Clang (библиотеки, которые они используют).

Вы заметите, что такая же ситуация применима и к std::vector<std::list<std::unique_ptr<int>>> (и, возможно, к другим).

2 голосов
/ 04 мая 2020

Чтобы обеспечить семантику перемещения для вектора, мы должны сообщить C ++ (в частности, std :: vector ), что конструктор перемещения и деструктор не генерирует, используя noexcept. Тогда конструктор перемещения будет вызываться при увеличении вектора. См. Это примечание:

Чтобы обеспечить гарантию строгих исключений, определяемые пользователем конструкторы перемещения не должны генерировать исключения. Например, std :: vector использует std :: move_if_noexcept для выбора между перемещением и копированием, когда необходимо переместить элементы.

Подробнее о том, что сказано в стандарте, читайте C ++ Переместить семантику и исключения

Если конструктор не noexcept , std :: vector не может использовать его, с тех пор он не может обеспечить гарантии исключений, требуемые стандартом.

В случае std :: map стандарт ничего не говорит о безопасности исключений для конструктора перемещения карты. Таким образом, компиляторы (в вашем случае, gcc и clang) могут помечать функции как не имеющие отношения к делу, независимо от того, предписывает ли это Стандарт или нет.

Об альтернативах или обходном пути см. Мой пример ниже (проверено с помощью gcc):

#include <vector>
#include <map>
#include <memory>

void foo(void) 
{
    std::vector<std::vector<std::unique_ptr<int>>> outer;
    std::vector<std::unique_ptr<int>> inner;
    std::unique_ptr<int> p = std::make_unique<int>(1);
    inner.emplace_back(std::move(p));
    outer.emplace_back(std::move(inner));
}

void bar(void) 
{
    std::vector<std::pair<std::unique_ptr<int>, std::unique_ptr<int>>> vec;
    std::unique_ptr<int> p1 = std::make_unique<int>(1);
    std::unique_ptr<int> p2 = std::make_unique<int>(2);

    auto pair = std::make_pair(std::move(p1), std::move(p2));

    vec.emplace_back(std::move(pair));
}

void bar2(void) 
{
    std::vector<std::unique_ptr<std::map<std::unique_ptr<int>, std::unique_ptr<int>>>> vec;
    std::unique_ptr<int> p1 = std::make_unique<int>(1);
    std::unique_ptr<int> p2 = std::make_unique<int>(2);

    auto map = std::make_unique<std::map<std::unique_ptr<int>, std::unique_ptr<int>>>();
    map->emplace(std::move(p1), std::move(p2));

    vec.emplace_back(std::move(map));
}

int main(int argc, char *argv[])
{
    foo();
    bar();
    return 0;
}

БОНУС:

Используйте emplace_back , когда это возможно. Он может быть быстрее (но часто это не так), он может быть более четким и компактным, но есть и некоторые подводные камни (особенно с неявными конструкторами).

...