vector<map<move-only type>> 不能用 MSVC 编译

vector<map<move-only type>> does not compile with MSVC

在 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();
}

GCC 和 Clang 对该代码没有问题,但 MSVC 无法编译。

正在寻找一种解决方法让我可以在所有主要编译器上编译。

标准未将 std::map 的移动构造函数定义为 noexcept。因此 std::vector 退回到使用复制构造函数(例如通过使用 std::move_if_noexcept)。

也就是说,编译器可以将函数标记为 noexcept,而不管标准是否强制要求。这可能是 GCC 和 Clang 所做的(他们使用的库)。

您会注意到同样的情况适用于 std::vector<std::list<std::unique_ptr<int>>>(也许还有其他)。

强制执行 move semantics for vector, we need to inform C++ (specifically std::vector) that the move constructor and destructor does not throw, using noexcept. Then the move constructor 将在向量增长时调用。请参阅此注释:

To make the strong exception guarantee possible, user-defined move constructors should not throw exceptions. For example, std::vector relies on std::move_if_noexcept to choose between move and copy when the elements need to be relocated.

有关标准内容的更多信息,请阅读 C++ Move semantics and Exceptions

如果构造函数不是 noexceptstd::vector 不能使用它,因为它不能确保标准要求的异常保证.

std::map 的情况下,标准没有说明映射的移动构造函数的异常安全性。因此,编译器(在您的情况下,gccclang)可以将函数标记为 noexcept,而与标准是否要求无关。

有关替代方案或解决方法,请参阅下面的示例(使用 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。它 可以 更快(但通常不是),它可以更清晰、更紧凑,但也有一些陷阱(尤其是非显式构造函数)。