导致代码无法编译的C++拷贝构造函数(gcc)

Ros*_*han 2 c++ constructor copy const reference

我有以下不编译的代码.编译器错误是:

"error: no matching function to call B::B(B)",
candidates are B::B(B&) B::B(int)"
Run Code Online (Sandbox Code Playgroud)

代码在以下两个条件之一下编译:

  1. 取消注释功能B(const B&)
  2. 将'main'更改为以下内容

    int main()
    {
            A a;
            B b0;
            B b1 = b0;
            return 0;
    }
    
    Run Code Online (Sandbox Code Playgroud)

如果我做1,代码编译,但从输出它说它调用'非const复制构造函数'.

谁能告诉我这里发生了什么?

using namespace std;
class B
{
    public:
    int k;
     B()
     { 
        cout<<"B()"<<endl; 
     }
     B(int k) 
     { 
        cout<<"B(int)"<<endl;
        this->k = k;
     }
     /*B(const B& rhs) { 
        cout<<"Copy constructor"<<endl;
        k = rhs.k;
     }*/
     B(B& rhs) 
     { 
        cout<<"non const Copy constructor"<<endl;
        k = rhs.k;
     }
     B operator=(B& rhs)
     {
        cout<<"assign operator"<<endl;
        k = rhs.k;
        return *this;
     }  
};

class A
{
    public:
        B get_a(void)
        {
            B* a = new B(10);
            return *a;
        }       
};

int main()
{
    A a;
    B b0 = a.get_a();  // was a.just();
    B b1 = b0 ;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

GRB*_*GRB 7

我已经对此做了一些额外的阅读,并且正如我一直怀疑的那样,之所以发生这种情况,是因为返回值优化.正如维基百科文章所解释的那样,RVO是允许编译器在分配临时对象或将其复制到永久变量的过程中消除临时对象的允许机制.此外,RVO是允许违反as-if规则的少数功能(如果不是唯一的)之一,只有编译器具有相同的可观察行为时才允许进行优化,就好像从未进行过优化一样.第一个地方 - 一个豁免,这是解释这里的行为的关键(我承认只是在我研究这个问题时才知道这个豁免,这就是我最初也感到困惑的原因).

在您的情况下,GCC足够聪明,可以避免两个副本中的一个.将代码简化为更简单的示例

B returnB()
{
    B a;
    B* b = &a;
    return *b;
}

int main()
{
    B c = returnB();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果一个遵循标准并且不执行RVO,则在制作过程中制作两个副本c- *binto returnB的返回值的副本,以及返回值的副本c.在您的情况下,GCC省略了第一个副本,而是只生成一个副本,*b直接进入c.这也解释了为什么B(B&)被调用而不是B(const B&)- 因为*b(又名a)不是临时值,编译器不再需要使用而是在构造时B(const B&)选择更简单的B(B&)调用c(非const过载总是自动优先于const过载如果选择存在).

那么,为什么编译器仍然会出错B(const B&)呢?这是因为在进行优化(如RVO)之前,代码的语法必须正确.在上述例子中,returnB() 返回一个临时的(根据C++语法规则),所以编译器必须看到一个B(const B&)拷贝构造.但是,一旦编译器确认您的代码在语法上是正确的,那么它就可以进行B(const B&)从未使用过的优化.

编辑:给Charles Bailey的帽子提示,他在C++标准中发现了以下内容

12.2 [class.temporary]:"即使避免创建临时文件,也必须尊重所有语义限制,就像创建临时对象一样."

这只是强化并确认了复制构造函数需要在复制临时构造时引用const(无论构造函数是否实际使用)