【问题标题】:C++11 non-static member initializers and deleted copy constructorC++11 非静态成员初始化器和删除的复制构造函数
【发布时间】:2013-02-16 17:01:33
【问题描述】:

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

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

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

    A a = 7;
    

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

    A a(A(7));
    

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

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

    A a{7};
    

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

    【讨论】:

      【解决方案2】:

      允许复制初始化以省略复制,但复制构造函数必须由标准访问。

      【讨论】:

        【解决方案3】:

        根据 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 限定的),则再次执行重载决议,将对象视为左值。 [ 注意:无论是否发生复制省略,都必须执行此两阶段重载解决方案。它决定了在不执行省略的情况下要调用的构造函数,并且即使省略了调用,所选构造函数也必须是可访问的。 —end note ] [...]

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

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

        struct B
        {
            A a{7};
        };
        

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

        【讨论】:

        • 感谢您的回答和修复。我唯一不明白的是为什么在这种情况下标准需要复制或移动构造函数?
        • @Simon:答案中解释了:需要创建一个临时对象,并从该临时对象构造初始化对象。因此,需要一个移动构造函数或复制构造函数。
        • 但是为什么要创建临时的,为什么不能只调用没有任何临时的构造函数 A(int) 呢?如果在构造函数中可以作为 B() : a(7){} 为什么在成员初始化中是不可能的?
        • @Simon:可以直接初始化成员,只需使用直接初始化语法:A{7}
        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

          所以说拷贝构造函数没有被使用,但是为什么我不能删除呢?

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

          【讨论】:

            猜你喜欢
            • 2016-04-19
            • 1970-01-01
            • 2015-01-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-04
            • 2015-05-22
            • 1970-01-01
            相关资源
            最近更新 更多