我可以强制默认的特殊成员函数为 noexexcept 吗?

Can I force a default special member function to be noexcept?

本文关键字:函数 noexexcept 成员 默认 我可以      更新时间:2023-10-16

以下结构无法在 C++11 下编译,因为我已将移动赋值运算符声明为 noexcept

struct foo
{
  std::vector<int> data;
  foo& operator=(foo&&) noexcept = default;
};

编译器生成的默认移动赋值运算符noexcept(false),因为std::vector<int>的移动赋值也是noexcept(false)的。 这反过来又是由于默认分配器std::allocator_traits<T>:: propagate_on_container_move_assignment设置为 std::false_type . 另请参阅此问题。

我相信这已在 C++14 中修复(请参阅库缺陷 2103)。

我的问题是,有没有办法让我强制noexcept默认移动分配运算符而无需自己定义它?

如果这是不可能的,有没有办法欺骗std::vector<int> noexcept移动可分配,以便noexcept(true)传递到我的结构中?

我相信这已在 C++14 中修复(请参阅库缺陷 2103)。

作为DR,该修复应被视为对C++11的更正,因此某些C++11实现已经修复了它。

我的问题是,有没有办法让我强制noexcept默认的移动分配分配运算符,而无需自己定义它?

noexcept默认的移动分配运算符,您需要使其子对象具有noexcept移动分配运算符。

我能想到的最明显的便携式方法是在std::vector周围使用包装器,这会强制移动noexcept

template<typename T, typename A = std::allocator<T>>
  struct Vector : std::vector<T, A>
  {
    using vector::vector;
    Vector& operator=(Vector&& v) noexcept
    {
      static_cast<std::vector<T,A>&>(*this) = std::move(v);
      return *this;
    }
    Vector& operator=(const Vector&) = default;
  };

另一个类似的选项是使用 DR 2013 修复程序定义您自己的分配器类型并使用它:

template<typename T>
  struct Allocator : std::allocator<T>
  {
    Allocator() = default;
    template<typename U> Allocator(const Allocator<U>&) { }
    using propagate_on_container_move_assignment  = true_type;
    template<typename U> struct rebind { using other = Allocator<U>; };
  };
template<typename T>
  using Vector = std::vector<T, Allocator<T>>;

另一种选择是使用标准库实现,例如GCC,它实现了DR 2013的分辨率,并且在已知所有分配器实例比较相等时,std::vector的移动分配运算符noexcept其他分配器类型。

我不认为你可以强迫任何东西,但你可以包装它:

#include <iostream>
#include <vector>
template <typename T>
struct Wrap
{
    public:
    Wrap() noexcept
    {
        new (m_value) T;
    }
    Wrap(const Wrap& other) noexcept
    {
        new (m_value) T(std::move(other.value()));
    }
    Wrap(Wrap&& other) noexcept
    {
        std::swap(value(), other.value());
    }

    Wrap(const T& other) noexcept
    {
        new (m_value) T(std::move(other));
    }
    Wrap(T&& other) noexcept
    {
        new (m_value) T(std::move(other));
    }
    ~Wrap() noexcept
    {
        value().~T();
    }
    Wrap& operator = (const Wrap& other) noexcept
    {
        value() = other.value();
        return *this;
    }
    Wrap& operator = (Wrap&& other) noexcept
    {
        value() = std::move(other.value());
        return *this;
    }
    Wrap& operator = (const T& other) noexcept
    {
        value() = other;
        return *this;
    }
    Wrap& operator = (T&& other) noexcept
    {
        value() = std::move(other);
        return *this;
    }
    T& value() noexcept { return *reinterpret_cast<T*>(m_value); }
    const T& value() const noexcept { return *reinterpret_cast<const T*>(m_value); }
    operator T& () noexcept { return value(); }
    operator const T& () const noexcept { return value(); }
    private:
    typename std::aligned_storage <sizeof(T), std::alignment_of<T>::value>::type m_value[1];
};

struct Foo
{
    public:
    Foo& operator = (Foo&&)  noexcept = default;
    std::vector<int>& data() noexcept { return m_data; }
    const std::vector<int>& data() const noexcept { return m_data; }
    private:
    Wrap<std::vector<int>> m_data;
};
int main() {
    Foo foo;
    foo.data().push_back(1);
    Foo boo;
    boo = std::move(foo);
    // 01
    std::cout << foo.data().size() << boo.data().size() << std::endl;
    return 0;
}

(感谢乔纳森·韦克利)