C ++构造函数调用和对象创建

时间:2011-09-17 19:51:37

标签: c++ initialization copy-constructor

class Test{
    public :
        int x;  
        Test()
        {
            x = 0;
            cout<<"constructor with no arguments called"<<endl;
        }
        Test(int xx)
        {
            x = xx;
            cout<<"constructor with single int argument called"<<endl;
        }

};


int main()
{
    Test a(10);
    Test aa = 10;
}

输出: 程序编译和输出

  

带有单个int参数的构造函数,名为

     

带有单个int参数的构造函数,名为

但现在

class Test{
    public :
        int x;  
        Test()
        {
            x = 0;
            cout<<"constructor with no arguments called"<<endl;
        }
        Test(int xx)
        {
            x = xx;
            cout<<"constructor with single int argument called"<<endl;
        }

        Test( Test& xx)
        {
            x = xx.x;
            cout<<"copy constructor called"<<endl;
        }


};


int main()
{
    Test a(10);
    Test aa = 10;
}

编译失败。

constructorinvokings.cc:36:7: error: no viable constructor copying variable of type 'Test'
        Test aa = 10;
             ^    ~~
constructorinvokings.cc:23:3: note: candidate constructor not viable: no known conversion from 'Test' to 'Test &' for 1st
      argument
                Test( Test& xx)
                ^
1 error generated.

我是C ++的新手。

不是测试a(10)和测试aa = 10;相同吗?

为什么添加复制构造函数与Test aa = 10?

冲突

如果我将Test(Test&amp; xx)修改为Test(const Test&amp; xx)它正在工作。但是,当我们尝试使用整数参数调用构造函数时,为什么编译器会检查复制构造函数签名。

请澄清

提前致谢。

4 个答案:

答案 0 :(得分:6)

到目前为止,你得到的所有答案都过于复杂和/或有些误导。

在第一次构建/初始化中:

T a(10);

非常明显的事情发生了。第二个构造/初始化更有趣:

T aa = 10;

这相当于:

T aa(T(10));

这意味着您创建了一个T类型的临时对象,然后将aa构造为此临时对象的副本。这意味着将调用复制构造函数。

当没有显式声明时,C ++有一个默认的复制构造函数。因此,即使class T的第一个版本没有声明复制构造函数,仍然有一个版本。编译器声明的那个具有此签名T(const T &)

现在,在第二种情况下,您声明了类似于复制构造函数的内容,您将参数设为T &,而不是const T &。这意味着编译器在尝试编译第二个表达式时会尝试使用您的复制构造函数,但它不能。它抱怨你声明的复制构造函数需要一个非const参数,并且它给出的参数是const。所以它失败了。

另一个规则是在编译器将初始化转换为T aa(T(10));之后,然后允许将其转换为T aa(10);。这被称为“复制省略”。在某些情况下,允许编译器跳过对复制构造函数的调用。但它只能在验证表达式是否正确形成后才能执行此操作,并且在复制构造函数调用仍然存在时不会生成任何编译器错误。因此,这是一个优化步骤,可能会影响程序的哪些部分运行,但不能影响哪些程序有效以及哪些程序出错(至少从它们是否编译的角度来看)。

答案 1 :(得分:4)

测试a(10)并测试aa = 10;不一样。第一个构建一个10的测试,而第二个构建一个10个的测试,然后复制构造aa。添加复制构造函数会发生冲突,因为它声明复制构造是两个操作数的可变操作。有些情况下,复制构造函数将源作为非const引用,但是有些复杂的情况并不是你的情况。

编辑:请注意,在第二种情况下,允许编译器忽略该复制构造,通常会这样做。

答案 2 :(得分:2)

(更简洁的测试用例here。)

这两种初始化形式实际上并不完全等同。

前者是你的沼泽标准结构:

T o(10);

然而,后者更为复杂。

T o = 10;

此处,RHS(10)上的表达式转换为类型T,然后从该转换后的表达式复制构造对象o。这里不能进行复制构造,因为T(T&)禁止合成隐式T(T const&),并且RHS上的临时对象不能绑定到ref-to-non - const

现在,允许编译器忽略该复制构造,这通常会发生,但转换必须仍然有效或程序格式错误。


  

[n3290: 8.5/13]:初始化的形式(使用括号或=)   通常是微不足道的,但是当初始化器或者   被初始化的实体具有类类型;见下文。 [..]

     

[n3290: 8.5/14]:

形式出现的初始化
T x = a;
     

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

     

[n3290: 8.5/16]:初始值设定项的语义如下。 [..]   如果目标类型是(可能是cv限定的)类类型:[..]   否则(即表示剩余的复制初始化情况),   可以从源转换的用户定义的转换序列   键入目标类型或(使用转换函数时)   按照13.3.1.4 中的描述列举其派生类,   并且通过重载决策(13.3)选择最好的一个。 如果   转换不能完成或模糊,初始化是   形成不良。 [..]

     

[n3290: 12.8/31]:满足某些条件时,即实施   允许省略类对象的复制/移动构造,甚至   如果对象的复制/移动构造函数和/或析构函数具有   副作用。 [..]这种复制/移动操作的省略,称为复制   elision,在下列情况下允许(可能是   合并消除多份副本):[..]

     
      
  • 当一个尚未绑定到引用的临时类对象时   (12.2)将被复制/移动到具有相同的类对象   cv-unqualified类型,可以省略复制/移动操作   将临时对象直接构造到目标中   省略复制/移动。 [..]
  •   

(我实际上找不到一个引文明确指出,即使复制被删除,复制构造函数仍然必须可访问,但情况确实如此。)

答案 3 :(得分:-1)

我不同意K-ballo。当您使用单个参数定义c'tor时,编译器可以[mis]使用它来进行隐式转换。在这种情况下会发生什么。

因此,当您运行程序时,它会调用“带有单个arg的c'tor”来构建AWA aa。

  
    

测试aa = 10;

  

本声明中没有涉及“复制”或“转让”。 鉴于您已经为编译器提供了单个arg c'tor,在这种情况下它会错误地使用它。

// So basically compiler converts this:
Test aa = 10;
// to
Test aa(10);

你可以通过将c'tor标记为显式来阻止滥用。只需将Test( int x )更改为explicit Test( int x ) 请在此处查看显式关键字的一些解释:What does the explicit keyword mean in C++?

  
    

如果我将Test(Test&amp; xx)修改为Test(const Test&amp; xx)它正在工作。但是,当我们尝试使用整数参数调用构造函数时,为什么编译器会检查复制构造函数签名。

  

我也不明白这一点,想知道。 :)

我发现复制c'tor从未被调用过。

#include<iostream>
using namespace std;

class Test
{
public :
    int x;
    Test() { x=0; cout << "    Test() - " << x << endl;}
    Test(int xx) { x=xx; cout << "    Test(int xx) - " << x << endl;}
    Test(Test& xx){ x=xx.x; cout << "    Test(Test& xx) - " << x << endl;}
    Test(const Test& xx){ x=xx.x; cout << "    Test(const Test& xx) - " << x << endl;}
    Test& operator= (const Test& xx) { x=xx.x; cout << "    Test& operator= (const Test& xx) - " << x << endl; return *this;}
};


int main()
{
    cout << "--Test a(10);--" << endl;
    Test a(10);
    cout << "--Test aa = 20;--" << endl;
    Test aa = 20;
    cout << "--Test aaa = aa;--" << endl;
    Test aaa = aa;
    cout << "--aaa = aa;--" << endl;
    aaa = aa;
    cout << "--aaa = 30;--" << endl;
    aaa = 30;
}
/*
OUTPUT:
    --Test a(10);--
        Test(int xx) - 10
    --Test aa = 20;--
        Test(int xx) - 20
    --Test aaa = aa;--
        Test(Test& xx) - 20
    --aaa = aa;--
        Test& operator= (const Test& xx) - 20
    --aaa = 30;--
        Test(int xx) - 30
        Test& operator= (const Test& xx) - 30
*/

当const arg Test(const Test& xx)的复制c'tor被注释掉时:

我们收到编译错误。

D:\Workspaces\CodeBlocks\Test\main.cpp: In function 'int main()':
D:\Workspaces\CodeBlocks\Test\main.cpp:21:19: error: no matching function for call to 'Test::Test(Test)'
D:\Workspaces\CodeBlocks\Test\main.cpp:10:9: note: candidates are: Test::Test(Test&)
D:\Workspaces\CodeBlocks\Test\main.cpp:9:9: note:                 Test::Test(int)
D:\Workspaces\CodeBlocks\Test\main.cpp:8:9: note:                 Test::Test()
Process terminated with status 1 (0 minutes, 0 seconds)
4 errors, 0 warnings

第21行是Test aa = 20;