【问题标题】:vector<map<move-only type>> does not compile with MSVCvector<map<move-only type>> 不能用 MSVC 编译
【发布时间】:2020-08-16 12:47:41
【问题描述】:

制作一个只移动类型的映射向量在 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 无法编译。

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

【问题讨论】:

    标签: c++ vector visual-c++ move-semantics


    【解决方案1】:

    要对向量强制执行move semantics,我们需要使用noexcept 通知C++(特别是std::vector)移动构造函数和析构函数不会抛出。然后当向量增长时,move constructor 将被调用。请参阅此说明:

    为使强异常保证成为可能,用户定义的移动构造函数不应抛出异常。例如,当需要重新定位元素时,std::vector 依赖于 std::move_if_noexcept 在移动和复制之间进行选择。

    有关标准中的内容的更多信息,请阅读 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。它可以更快(但通常不是),它可以更清晰、更紧凑,但也存在一些缺陷(尤其是非显式构造函数)。

    【讨论】:

    • "如果构造函数不是 noexcept,std::vector 就不能使用它"——并非在所有情况下都是如此;它会使用它,即使它不是noexcept,如果它根本不能使用复制构造函数(提示;-))。
    • vector>> 是一个很好的解决方法,谢谢!
    • "该标准没有说明 [..] 的异常安全性。因此,编译器 [..] 可以将函数标记为 noexcept" 请参阅 @987654328 @。请注意,constexpr (constexpr.functions-1) 并非如此。
    【解决方案2】:

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

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

    您会注意到std::vector&lt;std::list&lt;std::unique_ptr&lt;int&gt;&gt;&gt;(可能还有其他)也有同样的情况。

    【讨论】:

    • 是的,我明白这个问题,只是想知道是否有解决方法。
    猜你喜欢
    • 2019-10-30
    • 1970-01-01
    • 2014-10-23
    • 2021-05-28
    • 1970-01-01
    • 1970-01-01
    • 2014-12-26
    • 1970-01-01
    相关资源
    最近更新 更多