在下面的代码中,包装器< T>声明对象被包含可移动的T,其中T是不完整的类型.可移动的析构函数是在没有T的完全知识的情况下才能实例化的,但是包装器的析构函数只能被前向声明,这意味着如果〜可移动()在〜包装的定义点被实例化就足够了).
#include <utility> template<class T> struct movable { movable() noexcept = default; ~movable() noexcept { (void) sizeof(T); } movable(const movable&) noexcept = delete; movable(movable &&) noexcept = default; }; template<class T> class wrapper { public: movable<T> m; wrapper() noexcept = default; wrapper(wrapper &&) noexcept = default; ~wrapper(); }; struct incomplete; int main() { /* extern */ wrapper<incomplete> original; wrapper<incomplete> copy(std::move(original)); }
但是,wrapper()想要实例化〜movable().我知道如果出现异常,则成员的销毁必须是可能的,但是可移动()和wrapper()都是noexcept.有趣的是,move构造函数工作正常(尝试取消注释示例代码中的extern部分.)
这种行为的原因是什么,有没有办法规避呢?
解决方法
如T.C.所观察到的,
In a non-delegating constructor,the destructor for […] each non-static data member of class type is potentially invoked […]
根据DR1424,动机是明确指出,如果析构函数从父对象的构造函数中无法访问,则需要执行一个错误,“[即使]根据给定的异常也不会抛出异常子对象的构造“.
可移动的< T>的析构函数是可访问的,但它不能被实例化,这是您的问题出现的地方,因为潜在的调用析构函数是odr使用的.
这使得实现者的生活更简单,因为它们只能验证每个子对象具有可访问和必要的可实例化析构函数,并将其留给优化器以消除不需要的析构函数调用.替代方案将是非常复杂的 – 根据是否有任何后续的子对象是不可构造的,并且在构造函数体上,将需要或不需要析构函数.
避免潜在调用析构函数的唯一方法是使用放置新的,自己接管子对象的生命周期管理:
#include <new> // ... template<class T> class wrapper { public: std::aligned_storage_t<sizeof(movable<T>),alignof(movable<T>)> m; wrapper() noexcept { new (&m) movable<T>; }; wrapper(wrapper&& rhs) noexcept { new (&m) movable<T>{reinterpret_cast<movable<T>&&>(rhs.m)}; } ~wrapper(); };