3

我正在尝试使用 GCC 4.7.2 (MinGW) 编译以下简单代码。这里我使用 C++11 特性——非静态成员初始化器:

#include <iostream>
using namespace std;

struct A
{
    int var;

    A()
    {
        cout << "A()\n";
    }

    A(int i)
    {
        cout << "A(int i)\n";
        var = i;
    }

    A(const A&) = delete;
};

struct B
{
    A a = 7;
};

int main()
{
    B b;
    cout << "b.a.var = " << b.a.var;
    return 0;
}

由于删除了此处不需要的复制构造函数,此代码无法编译。以下是错误:

main.cpp:27:11: error: use of deleted function 'A::A(const A&)'
main.cpp:13:5: error: declared here
main.cpp: In constructor 'constexpr B::B()':
main.cpp:25:8: error: use of deleted function 'A::A(const A&)'
main.cpp:13:5: error: declared here

如果我像这样实现复制构造函数:

A(const A& a)
{
    cout << "A(const A&)\n";
    var = a.var;
}

然后代码编译得很好,程序给了我预期的输出:

A(int i)
b.a.var = 7

所以这意味着没有使用复制构造函数,但是为什么我不能删除它呢?

编辑:感谢您的回答。如果我使用=. 要解决这个问题,我需要实现移动构造函数或使用直接初始化语法A a{7}

4

5 回答 5

4

初始化器为a您提供复制初始化:

A a = 7;

对于这样的复制初始化,如果需要用户定义的转换,则生成的初始化等效于:

A a(A(7));

也就是说,A构造一个临时对象,然后将其传递给a对象的复制构造函数。这种复制可能会被省略,但复制构造函数必须仍然可用。换句话说,只有在最初可能复制的情况下,才能省略复制。如果你delete是复制构造函数,复制是不可能的。

如果您执行以下操作,您将有更好的时间使用已删除的复制构造函数:

A a{7};

这会直接初始化,不需要复制构造函数。

于 2013-02-16T17:09:30.093 回答
3

允许复制初始化以省略复制,但标准要求复制构造函数可以访问。

于 2013-02-16T17:03:45.067 回答
2

根据 C++11 标准的第 12.2/14 段:

表单中发生的初始化

T x = a;

以及在参数传递、函数返回、抛出异常 (15.1)、处理异常 (15.3) 和聚合成员初始化 (8.5.1)中称为复制初始化。[注意:复制初始化可能会调用移动(12.8)。——尾注]

您的复制初始化无法编译的原因是在复制初始化期间需要创建一个临时对象(至少在逻辑上),并且应该从中构造正在初始化的对象。

现在所有以前的答案似乎都只关注复制构造函数,但这里的第一个问题是缺少move-constructor。只要您提供一个,那么复制构造函数确实不是必需的。

唉,删除复制构造函数会阻止生成隐式移动构造函数。显式添加一个可以解决问题:

struct A
{
    int var;

    A()
    {
        cout << "A()\n";
    }

    A(int i)
    {
        cout << "A(int i)\n";
        var = i;
    }

    A(const A&) = delete;

    // THIS MAKES IT WORK
    A(A&& a)
    {
        cout << "A(A&&)\n`;
        var = a.var;
    }
};

请注意,当移动构造函数和复制构造函数都存在时,移动构造函数是首选,因为为复制初始化对象而创建的临时对象是右值。

当移动构造函数不存在时,编译器可以调用复制构造函数来执行初始化,因为常量左值引用可以绑定到右值引用,并且复制被视为未优化的移动。

但是,即使允许编译器省略对移动或复制构造函数的调用,仍必须检查操作的语义。根据 C++11 标准的第 12.8/32 段:

当满足或将满足复制操作的省略标准时,除了源对象是函数参数的事实,并且要复制的对象由左值指定时,选择复制的构造函数的重载决策是首先执行好像对象是由右值指定的。如果重载决议失败,或者如果所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是 cv 限定的),则再次执行重载决议,将对象视为左值。[注意:无论是否会发生复制省略,都必须执行此两阶段重载解析。它确定如果不执行省略则要调用的构造函数,并且即使调用被省略,所选构造函数也必须是可访问的。——尾注] [...]

因此,如果移动构造函数和复制构造函数都不存在,编译器会发出错误。

但是,如果您愿意,您可以直接初始化您的对象,而不是复制初始化它。只需使用直接初始化语法:

struct B
{
    A a{7};
};

这将使移动构造函数和复制构造函数变得不必要,因为直接初始化对象时不会创建临时对象。

于 2013-02-16T17:38:45.297 回答
1

那么就是说没有使用拷贝构造函数,但是为什么不能删除呢?

在您的情况下,复制构造函数仅用于标准要求的语义检查,它也需要是可访问的。稍后,编译器优化代码,省略了对复制构造函数的调用,因此实际上并没有调用它。

于 2013-02-16T17:03:31.463 回答
1

由于删除了此处不需要的复制构造函数,此代码无法编译

抱歉,但您的复制构造函数必需的。即使副本可以优化出来,它仍然必须在代码中是可能的。这是语言规定的。

于 2013-02-16T17:07:53.133 回答