在MSVC 2017中移动STL容器的构造函数未标记为noexcept

时间:2017-12-02 02:41:17

标签: c++ visual-studio c++11 visual-studio-2017

我正在将我的项目从VS2015移动到VS2017,当然这并不顺利。

我看到奇怪的编译器错误,可以通过以下代码重现:

struct MoveOnly
{
    MoveOnly() {}
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly& operator = (const MoveOnly&) = delete;
    MoveOnly(MoveOnly&&) = default;
    MoveOnly& operator = (MoveOnly&&) = default;
    bool operator == (const MoveOnly& rhs)const{return false;}
};
struct Hasher
{
    size_t operator()(const MoveOnly&)const{return 0;}
};
std::vector < std::unordered_map<MoveOnly, int, Hasher> > test;
test.emplace_back();

我可以使用所有编译器(gcc 7.2,clang 5.0.0,icc 18以及MSVC 2015)成功编译此代码。请点击此链接查看测试: https://godbolt.org/g/uSqwDJ。但是在MSVC 2017(19.10.25017)上,有一个错误是由编译器尝试引用MoveOnly类型的已删除副本构造函数引起的。这个错误对我来说没有多大意义,因为没有理由在这里复制任何东西而不是移动。 /std:c++14/std:c++17/std:c++latest没有帮助。 gcc和clang正确处理代码的事实也让我怀疑msvc 2017编译器。

更新

Yakk发现问题后,我尝试使用其他容器代替unordered_map,代码只编译vector

1 个答案:

答案 0 :(得分:5)

所以问题似乎是这样的:

static_assert( noexcept(std::unordered_map<MoveOnly, int, Hasher>( std::declval<std::unordered_map<MoveOnly, int, Hasher>&&>())), "");

您的编译器认为std::unordered_map<MoveOnly, int, Hasher>不能noexcept移动构建。

然后,MSVC 2017附带的偏执std::vector实现依赖于复制元素,以便在向量调整大小时生成强大的异常保证。

显然无法复制。

现在std::unordered_map<int, int>也是如此 - MSVC认为移动它冒着抛出异常的风险;我相信你无法用密钥或散列类型做任何事情都可能使unordered_map异常安全的移动构造函数。

没有充分的理由让unordered_map(unordered_map&&) 成为noexcept。我不确定标准是允许它还是强制它,或者它是否是编译器中的错误;如果标准要求它为noexcept(false),那么标准就有缺陷。

您可以通过存储unique_ptr s的向量来解决此问题。或者编写一个包含异常的value_ptr,但代码更改次数更少:

template<class T>
struct value_ptr {
    std::unique_ptr<T> raw;
    value_ptr() noexcept(true)
    {
      try {
        raw = std::make_unique<T>();
      } catch (...) {}
    }
    template<class T0, class...Ts,
        std::enable_if_t<!std::is_same<value_ptr, std::decay_t<T0>>{}, bool> =true
    >
    value_ptr(T0&& t0, Ts&&...ts) noexcept(true)
    {
      try {
        raw=std::make_unique<T>( std::forward<T0>(t0), std::forward<Ts>(ts)... )
      } catch(...) {}
    }
    value_ptr(value_ptr&& o)noexcept(true)=default;
    value_ptr(value_ptr const& o)noexcept(true)
    { try {
      if (o.raw)
        raw = std::make_unique<T>(*o.raw);
      }catch(...){}
    }
    value_ptr& operator=(value_ptr&& o)noexcept(true)=default;
    value_ptr& operator=(value_ptr const& o)noexcept(true)
    { try {
      if (o.raw)
        raw = std::make_unique<T>(*o.raw);
      }catch(...){}
      return *this;
    }
    T* operator->() const { return raw.get(); }
    T& operator*() const { return *raw; }
    explicit operator bool() const { return (bool)raw; }
};

这是我的测试工具:

template<class M>
void test_M() {
  static_assert( noexcept(M( std::declval<M&&>())), "");
  std::vector < M > test;
  test.emplace_back();

}
void foo()
{
  using M0=value_ptr<std::unordered_map<MoveOnly, int, Hasher>>;
  using M1=std::unique_ptr<std::unordered_map<MoveOnly, int, Hasher>>;
  test_M<M0>();
  test_M<M1>();
}