C ++ 11非静态成员初始值设定项和已删除的复制构造函数

时间:2013-02-16 17:01:33

标签: c++ c++11

我正在尝试使用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}

5 个答案:

答案 0 :(得分:4)

a的初始化程序为您提供了复制初始化:

A a = 7;

对于这种需要用户定义转换的复制初始化,结果初始化等同于:

A a(A(7));

即构造临时A,然后传递给a对象的复制构造函数。可以省略此复制,但复制构造函数必须可用。换句话说,如果首先可以复制,则只能省略复制。如果您delete复制构造函数,则无法复制。

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

A a{7};

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

答案 1 :(得分:3)

允许复制初始化删除副本,但标准可以访问复制构造函数。

答案 2 :(得分: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;
    }
};

请注意,当move-constructor和copy-constructor都存在时,首选,因为为复制初始化对象而创建的临时值是一个rvalue。

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

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

  

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

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

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

struct B
{
    A a{7};
};

这将使move-constructor和copy-constructor变得不必要,因为直接初始化对象时不会创建临时。

答案 3 :(得分:1)

  

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

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

答案 4 :(得分:1)

  

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

抱歉,您的复制构造函数是必要的。尽管可以优化副本,但仍然可以在代码中实现。这是由语言强制执行的。