C++函数返回的临时对象用来初始化类对象是否调用拷贝构造函数
正常情况下,如果一个函数返回一个类对象,那它会生成一个临时对象进行拷贝。
最近,被这个问题纠结了很久,为什么纠结,因为遇到的代码输出和我预想的并不一样,一度以为并没有临时对象这个东西。
还好终于搞明白了临时对象初始化类对象调用拷贝构造函数的问题!下面是我解疑过程中遇到的代码:
#include <iostream>
using namespace std;
class Test
{
public:
int a;
Test(int x)
{
a = x;
cout << "default constructor" << endl;
}
Test(const Test &test)
{
cout << "copy constructor" << endl;
a = test.a;
}
};
void fun1(Test test)
{
cout << "fun1()..." << endl;
}
Test fun2()
{
Test t(2);
cout << "fun2()..." << endl;
return t;
}
int main()
{
Test t1(1);
Test t2 = t1;
cout << "before fun1()..." << endl;
fun1(t1);
Test t3 = fun2();
cout << "after fun2()..." << endl;
return 0;
}
如果用常见的g++ file.cpp去编译,则会出现如下结果:
default constructor // 来自35行:Test t1(1)
copy constructor // 来自36行:Test t2 = t1
before fun1()... // 来自37行:cout << "before fun1()..." << endl;
copy constructor /* 来自21行:这里调用fun1(),因为这里是值传递,在实参传给形参的时候,
需要在给fun1()开辟的栈内存里将实参拷贝给形参,
因此这里调用一次拷贝构造 */
fun1()... // 来自23行:cout << "fun1()..." << endl;
default constructor // 来自28行:调用fun2()时,函数内部采用默认(有参)构造实例化了一个对象
fun2()... //来自29行:
after fun2()... //来自41行:
这里就存在一个问题,输出结果,和我们预想的完全不一样。因为,无论考不考虑return的临时对象,第40行无论如何也会调用一个拷贝构造初始化t3才对。但是实际输出确实一个拷贝构造都没有调用?
实际答案当然不是一个都没调用!!!但是为什么呢?
其原因是:RVO(return value optimization),被G++进行值返回的优化了,具体的RVO的相关技术,可以进行百度。
我们可以将RVO优化关闭,可以对g++增加选项-fno-elide-constructors
,重新编绎之后,
执行结果如下:
default constructor
copy constructor
before fun1()...
copy constructor
fun1()...
default constructor
fun2()...
copy constructor
copy constructor
after fun2()...
这回就明白了吧。
此外,还有许多其他的坑需要注意,例如:
#include <iostream>
using namespace std;
class B
{
public:
B()
{
cout << "default constructor" << endl;
}
~B()
{
cout << "destructed" << data << endl;
}
B(const B &other)
{
this->data = other.data;
cout << "copy constructor" << endl;
}
B(int i) : data(i)
{
cout << "constructed by parameter" << data << endl;
}
int data;
};
B fun(B b)
{
return b;
}
B fun2()
{
B b(2);
return b;
}
int main()
{
B t1 = fun(5);
t1.data = 100;
B t2 = fun(t1);
//B t2 = fun2();
t2.data = 200;
return 0;
}
执行结果如下:
constructed by parameter5 /*44行:这里有一个非常需要注意的点,对函数fun()的参数输入,直接输入
有参构造对应的变量类型的时候,编译器会先调用有参构造创建一个临时的对象
(为啥是临时的呢,因为在函数调用结束后,被析构了),然后再将该对象拷贝
给形参,然后在return的时候又调用一次拷贝构造创建一个临时对象,然后回到
主调函数,又调用一次拷贝构造将该临时对象拷贝给t1。所以下面出现了三次调
用拷贝构造的记录*/
copy constructor
copy constructor
copy constructor
destructed5 //第一次析构,从栈考虑,return创建的临时对象是最后创建的,因此是最后入
栈的,所以先出栈。因此,这里析构的return的时候创建的临时对象。
destructed5 //第二次析构,析构形参
destructed5 //第三次析构,析构初始化形参时创建的临时对象
copy constructor //这三次拷贝构造均来自46行。第一次,实参被用于初始化形参;
copy constructor //第二次,return返回时创建临时对象;
copy constructor //第三次,回到主调函数,初始化t2。
destructed100 //析构fun2()return时创建的临时对象;
destructed100 //析构fun2()的形参。
destructed200 //析构t2
destructed100 //析构t1
还有一个有趣的事情:
#include <iostream>
using namespace std;
class Point
{
public:
Point(int=0,int=0);
Point (const Point&);
void displayxy();
~Point();
private:
int X,Y;
};
//点类的实现部分
Point::Point (int x,int y)
{
X=x;
Y=y;
cout<<"Constructor is called! "; cout<<this<<endl;
displayxy();
}
Point::Point(const Point& p){
X=p.X;
Y=p.Y;
cout<<"Copy constructor is called! "; cout<<this<<endl;
displayxy();
}
Point::~Point(){
cout<<"Destructor is called! "; cout<<this<<endl;
displayxy();
}
void Point::displayxy(){
cout<<"("<<X<<","<<Y<<")"<<endl;
}
Point func( Point p){
int x=10*2;
int y=10*2;
Point pp(x,y);
cout<<"......pp的地址为:"<<&pp<<endl;
return pp;
}
int main(){
Point p1(3,4);
Point p2=func(p1);//用类的一个对象去初始化该类的另一个对象时。
cout<<"......p2的地址为:"<<&p2<<endl;
return 0;
}
执行结果如下:(这里采用ROV的结果展现,主要是为了简洁)
Constructor is called! 0x7fffa53480b0
(3,4)
Copy constructor is called! 0x7fffa53480c0
(3,4)
Constructor is called! 0x7fffa53480b8
(20,20)
......pp的地址为:0x7fffa53480b8
Destructor is called! 0x7fffa53480c0
(3,4)
......p2的地址为:0x7fffa53480b8
Destructor is called! 0x7fffa53480b8
(20,20)
Destructor is called! 0x7fffa53480b0
(3,4)
从结果中看到,pp这个临时对象的地址,最后居然是p2的地址!
事实上,我们在创建类对象的时候,会给对象分配一个内存,但这里因为func函数返回了一个临时对象,这个临时对象的内存暂时存在,在我们初始化新对象p2时,给它分配的内存正好可以用这个来代替,因此这里似乎就没有输出p2的构造函数,实际上已经用掉了临时对象的内存啦!
不过这种情况并不适用于内建数据类型,例如:
#include <iostream>
using namespace std;
int fun(int a)
{
int b = a*a;
cout << "fun():" << &b << endl;
return b;
}
int main()
{
int a = 10;
int c = fun(a);
cout << "main()" << &c << endl;
return 0;
}
执行结果如下:
fun():0x7ffc197d5f84
main()0x7ffc197d5fa0