为什么添加析构函数(甚至是空的)会破坏我的结构,该结构使用 ref 转发和折叠来保存 ref 或值的副本?

Why does adding a destructor (even empty) break my struct that uses ref forwarding and collapsing to hold either a ref or copy of a value?

本文关键字:ref 结构 转发 添加 折叠 副本 保存 析构函数 为什么 我的      更新时间:2023-10-16

我正在创建一个类来存储对对象的引用(如果在构造函数中给定一个左值(,如果给定一个右值,则存储一个副本。这很好,可以编译:

template <typename obj_t>
struct holder
{
obj_t obj;
template <typename obj_ref_t>
holder(obj_ref_t && o)
: obj(std::forward<obj_ref_t>(o))
{}
};
template <typename obj_t>
holder<obj_t> make_held(obj_t && o) {
return holder<obj_t>(std::forward<obj_t>(o));
}

但是,当我向类添加一个析构函数(甚至是空析构函数(时,我收到一个编译器错误:

Invalid instantiation of non-const reference of type "obj_t &" from an rvalue of type "holder<obj_t &>"

为什么添加析构函数会以某种方式调用构造函数调用以从已包装的对象创建包装的对象?

构造函数模板永远不能是复制/移动构造函数

template <typename obj_ref_t>
holder(obj_ref_t && o)                  // not a move constuctor

当析构函数定义不存在时,您将隐式定义的移动构造函数用于holder。用户定义的析构函数的存在将禁止此隐式移动构造函数定义。

现在,唯一剩下的可行候选者是上面的转换构造函数模板,但如果obj_ref_t的类型为holder<T>,则不起作用,需要此构造函数来初始化make_held的返回值。所以这样的调用失败了

auto h = make_held(42);

简而言之,不要定义析构函数,或者如果必须定义移动构造函数。


定义像您拥有的构造函数模板这样的构造函数模板时,请注意,在某些情况下,它们可能优于移动构造函数。关于这个问题,SO 上有很多答案,这里有一个描述了问题,并有一个约束构造函数模板的解决方案。


如果要使用 C++17 编译器编译代码,由于保证复制省略,即使使用析构函数定义也可以编译上述代码。