为什么编译器需要复制构造函数,需要并且移动一个并且不使用它们中的任何一个?

Sap*_*Sap 2 c++ constructor copy-constructor move-constructor c++11

我已经尝试过问这个问题,但我还不够清楚.所以这是另一个尝试.我对我的英语很抱歉;)

我们来看看代码:

#include <iostream>
#include <memory>
using namespace std;

struct A {
    unique_ptr<int> ref;

    void printRef() {
        if (ref.get())
            cout<<"i="<<*ref<<endl;
        else
            cout<<"i=NULL"<<endl;
    }

    A(const int i) : ref(new int(i)) { 
        cout<<"Constructor with ";
        printRef();
    }
    ~A() {
        cout<<"Destructor with";
        printRef();
    }
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

它无法编译,因为unique_ptr已删除复制构造函数.
奥利?
这个类有一个隐含的移动构造函数,因为unique_ptr有一个.

我们来做一个测试:

#include <iostream>
#include <memory>
using namespace std;

struct A {
    unique_ptr<int> ref;

    void printRef() {
        if (ref.get())
            cout<<"i="<<*ref<<endl;
        else
            cout<<"i=NULL"<<endl;
    }

    A(const int i) : ref(new int(i)) { 
        cout<<"Constructor with ";
        printRef();
    }
    // Let's add a moving constructor.
    A(A&& a) : ref(std::move(a.ref)) { 
        cout<<"Moving constructor with";
        printRef();
    }
    ~A() {
        cout<<"Destructor with";
        printRef();
    }
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

我添加了一个移动构造函数,现在代码可以编译和执行.
即使不使用移动构造函数.
输出:

构造函数为i = 0
构造函数,其中i = 1
析构函数,其中i = 1 析构函数,其中i
= 0

好的......
让我们再做一次测试并删除复制构造函数(但保留移动构造函数).
我没有发布代码,只添加了一行:

A(const A& a) = delete;
Run Code Online (Sandbox Code Playgroud)

你应该相信我 - 它有效.因此编译器不需要复制构造函数.但它确实做到了!(一个facepalm应该在这里)
所以发生了什么?我觉得这完全不合逻辑!还是有一些我看不到的扭曲逻辑?

再一次:
unique_ptr有一个移动的构造函数,并有一个删除的复制构造函数.编译器需要复制构造函数.但实际上编译器需要一个移动构造函数(即使它没有被使用)并且不需要复制(因为它可以被删除).正如我所看到的那样,移动的构造函数(应该是?)隐含地存在.
这有什么问题?

PS还有一件事 - 如果我删除移动构造函数,则程序无法编译.所以移动构造函数是必需的,但不是复制构造函数.
如果禁止在那里使用它,为什么需要copy-constructor ?

PPS非常感谢juanchopanza的回答!这可以通过以下方式解决:

A(A&& a) = default;
Run Code Online (Sandbox Code Playgroud)

还要非常感谢Matt McNabb.
正如我现在看到的那样,移动构造函数不存在,因为unique_ptr没有复制,类具有析构函数(一般规则是默认情况下,默认/复制/移动构造函数和析构函数只能一起生成).然后编译器不会停止移动一个(为什么?!)并回退到复制一个.此时,编译器无法生成它并在没有其他任何操作的情况下因错误(关于复制构造函数)而停止.

顺便说一句,你添加:

A(A&& a) = delete;
A(const A& a) = default;
Run Code Online (Sandbox Code Playgroud)

它无法编译时出现有关'A :: A(A && a)'删除的错误,复制构造函数不会回退.

PPPS最后一个问题 - 为什么它会在COPY构造函数中停止而不是MOVE构造函数?!
GCC++ 4.7/4.8说:" 错误:使用已删除的函数'A :: A(const A&)' "
因此它在复制构造函数处停止.
为什么?!应该有' A :: A(A &&) '

好.现在,这似乎是一个关于移动/复制建设者选择规则的问题.
我在这里创建了更具体的新问题

M.M*_*M.M 5

这称为复制省略.

在这种情况下,规则是指定了复制/移动操作,但是编译器被允许可选择的Elid它作为一种优化,即使复制/移动构造有副作用.

当发生复制省略时,通常直接在目的地的存储空间中创建对象; 而不是创建一个新对象,然后将其复制/移动到目标并删除第一个对象.

复制/移动构造函数仍然必须存在,否则我们最终会遇到代码似乎编译的愚蠢情况,但是当编译器决定不执行copy-elision时,则无法编译.或者代码可以在某些编译器上运行并在其他编译器上运行,或者如果您使用不同的编译器开关.


在第一个示例中,您不会声明副本或移动构造函数.这意味着它获得了一个隐式定义的拷贝构造函数.

然而,有一个规则,如果一个类有一个用户定义的析构函数,然后它并没有得到一个隐式定义的移动构造函数.不要问我为什么存在这个规则,但它确实存在(参见[class.copy]#9作为参考).

现在,标准的确切措辞在这里很重要.在[class.copy]#13中它说:

如果使用了odr-used(3.2),则默认定义一个默认且未定义为已删除的复制/移动构造函数

[注意:即使实现省略了odr-use(3.2,12.2),也会隐式定义复制/移动构造函数. - 注意

odr-used的定义非常复杂,但它的要点是,如果你从不尝试复制对象,那么它将不会尝试生成隐式定义的复制构造函数(同样也用于移动和移动).

正如TC在您之前的线程中解释的那样,做的操作A a[2] = {0, 1};确实指定了复制/移动,即a[0]必须通过复制或移动来临时初始化值A(0).这个临时版本可以进行复制省略,但正如我之前解释的那样,正确的构造函数必须仍然存在,以便在这种情况下编译器决定不使用复制省略时代码可以工作.

由于您的类在此处没有移动构造函数,因此无法移动它.但到临时绑定的构造函数中的尝试A仍然成功,因为定义(尽管隐式定义的)一个拷贝构造函数.此时,odr-use会发生并且它会尝试生成复制构造函数并因此而失败unique_ptr.


在第二个示例中,您提供了一个move-constructor但没有copy-constructor.仍有一个隐式声明的复制构造函数,它在被使用之前不会被生成,就像之前一样.

但是重载决策的规则说如果副本和移动都是可能的,那么使用移动构造函数.所以在这种情况下它并没有使用复制构造函数,一切都很好.

在第三个示例中,move-constructor再次赢得重载决策,因此无论copy-constructor的定义方式如何.