C++ 函数返回对象时并没有调用拷贝构造函数

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
  • 34
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值