是啥导致移动构造函数被删除

Posted

技术标签:

【中文标题】是啥导致移动构造函数被删除【英文标题】:What is causing the move constructor to be deleted是什么导致移动构造函数被删除 【发布时间】:2017-02-01 22:54:37 【问题描述】:

我有以下示例:

#include <vector>

class noncopyable 
protected:
    noncopyable() 
    ~noncopyable() 
    noncopyable(const noncopyable&) = delete;
    noncopyable& operator=(const noncopyable&) = delete;
    noncopyable(noncopyable&&) = default;
    noncopyable& operator=(noncopyable&&) = default;
;

class C1 : private noncopyable 
public:
  C1()  
  ~C1()  
;

int main() 
    std::vector<C1> v;
    v.emplace_back();
    return 0;

我认为它应该可以工作,因为C1 应该是可移动的,因为它是基类并且它没有数据成员。相反,我得到了一个错误(使用 clang++):

error: call to implicitly-deleted copy constructor of 'C1'
     ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); 
                                     ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
.
.
.
note: in instantiation of function template specialization 'std::vector<C1, std::allocator<C1> >::emplace_back<>' requested here
        v.emplace_back();
          ^
note: copy constructor of 'C1' is implicitly deleted because base class 'noncopyable' has a deleted copy constructor
class C1 : private noncopyable 
           ^
note: 'noncopyable' has been explicitly marked deleted here
        noncopyable(const noncopyable&) = delete;

做一点研究 (http://en.cppreference.com/w/cpp/language/move_constructor) 发现如果有用户定义的析构函数,则不会定义隐式移动构造函数。这似乎是这里的问题,因为C1 有一个析构函数,所以没有定义移动构造函数。果然,如果我删除析构函数或将C1(C1&amp;&amp;) = default; 添加到C1,那么它就可以工作了。

到目前为止一切顺利。

问题是错误消息没有提到~C1() 或移动构造函数。它说它正在尝试调用复制构造函数,该构造函数在基类中被删除。所以我尝试将noncopyable 中的deleteed 函数改为defaulted,并且(惊喜!),这也解决了错误。

所以我的问题是,最后这件事与错误或纠正有什么关系?如果有析构函数,基类有没有拷贝构造函数有什么区别?

【问题讨论】:

尝试添加 noexcept 说明符。 请注意“没有移动构造函数”和“有已删除的移动构造函数”之间的区别。 【参考方案1】:

你不需要vector,一个更简单的例子就是:

C1 a;
C1 b(std::move(a)); // error: C1's copy constructor is deleted

来自[class.copy]:

如果X 类的定义没有显式声明移动构造函数,当且仅当 (9.1) — X 没有用户声明的复制构造函数, (9.2) — X 没有用户声明的复制赋值运算符, (9.3) — X 没有用户声明的移动赋值运算符,并且(9.4) — X 没有用户声明的析构函数。

C1 有一个用户声明的析构函数,因此它没有移动构造函数。 C1 确实但是有一个隐式声明的复制构造函数

如果类定义没有显式声明复制构造函数,则隐式声明非显式构造函数。如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的副本 构造函数被定义为删除; 否则,它被定义为默认(8.4)。如果类具有用户声明的复制赋值运算符或用户声明的析构函数,则不推荐使用后一种情况。

C1 上的完整构造函数集,显式和隐式,如下所示:

C1();
C1(C1 const& ) = default; // but also delete
~C1();

因此,尝试从 C1 类型的右值构造 C1 将匹配隐式声明的复制构造函数作为最佳匹配(没有其他方法可行),但该构造函数是 deleted,因为 noncopyable 的复制构造函数是deleted,所以整个表达式格式不正确。

这就是错误消息提到构造函数的原因。该移动构造是格式错误的,因为该移动构造的最佳匹配是复制构造函数格式错误。它不能提及移动构造函数,因为没有移动构造函数,而析构函数与手头的表达式无关。当您将基类更改为可复制时,现在C1 也变为可复制 - 所以没有错误。仍然没有移动构造函数,只是现在有一个可行的移动构造候选者。

【讨论】:

构造函数如何既是default又是delete?另外,你能解释一下你第二段引文中的最后一句话吗?案例“已弃用”是什么意思?这是否意味着如果我有一个用户定义的析构函数,我将不会获得隐式复制构造函数(与移动构造函数相同)? @baruch 它是默认的。但是默认格式不正确,因此被删除。弃用只是意味着在未来某个不确定的时间点,功能将会改变。别担心。为了完整起见,我只是将其包括在内。

以上是关于是啥导致移动构造函数被删除的主要内容,如果未能解决你的问题,请参考以下文章

c#中对象初始化是啥意思?

由于复制构造函数/删除数组导致内存损坏?

为啥定义了移动构造函数而隐式删除了赋值运算符?

了解 Lambda 闭包类型如何删除默认构造函数

将对象的指针提供给构造函数会导致对象被破坏[重复]

c++的复制构造函数