深度探索C++对象模型读书笔记——2.3 程序转化语意学

本文探讨了C++中初始化的三种情况,包括显式初始化、函数形参初始化和返回值初始化,并通过实例分析了C++编译器如何处理返回值的拷贝。文章特别提到了非返回值优化(NRV)在不同编译器中的实现差异,以VS2008为例,展示了NRV优化对代码效率的影响,以及在VS2008中未进行NVR优化的情况。同时,对比了两种不同的初始化方式在效率上的显著区别。
摘要由CSDN通过智能技术生成

  • C++中初始化有三种情形:

1.显式初始化,如:

A a(tmp);
A a=tmp
A a=A(tmp);

2. 函数形参的初始化

Standard 规定形式相当于:A a = arg;

3. 返回值的初始化

Standard 规定形式相当于:A a = ret;

已知如下函数定义

X bar()

{

X xx;

//处理xx ...

return xx;

}

bar()的返回值如何从局部对象 xx中拷贝过来呢?cfront的做法是进行如下两步骤的转化:

1.加上一个额外参数,类型是class object的一个reference,这个参数将用来放置

“拷贝建构(copy constructed)”而得的返回值

2.在return指令之前安插一个copy constructor调用操作.

//函数转换
//以反映出copy constructor的应用。
//c++伪码
void bar(X & _result)
{
	X xx;
	//编译器所产生的default constructor调用操作
	xx.X::X();
	//......处理xx
	//编译器所产生的copy constructor调用操作
	_result.X::x(xx);
	return;
}

cfront编译器必须转换每一个bar()调用操作,以反映其新定义。如

X xx=bar();将转变成

X xx;

bar(xx);

那VC++上的编译器也是这么实现的吗???在VS2008上我写了如下代码以进行验证:

#include <cstdio>
using namespace std;
class A
{
public:
	A(){printf("A()\n");}
	A(const A &rhs)
	{
		a=rhs.a;
		printf("A(A)\n");
	}
private:
	int a;
};
A f()
{
	A a;
	return a;
}


int main()
{
	f();
	printf("\n");
	A a=f();
}

程序输出为
/*
A()
A(A)

A()
A(A)
/*

先看f()的调用。输出"A(A)"说明调用了Copy Constructor,因此有类似_result.A::A(a)的语句被执行了,所以变量a必然被创建了,"A()"就是a被默认构造函数构造出来时输出的。这时候又有个疑问,_result是一个引用,既然它能执行_result.A::A(a),肯定是因为它指向了某个变量,那它指向了哪个变量呢(注:这个变量不可能是a,否则不会调用Copy Constructor)?又既然_result指向了某个变量,那么这个变量必然要在栈中要有内存空间,那么为什么这个变量没有调用默认构造函数?(否则输出应该有两个"A()")。
我的解释是编译器在这种情况下开辟了一块内存大小为sizeof(A)的内存,然后再让引用_result指向这块内存,但没有调用默认构造函数,因为没有必要对这块内存进行默认初始化。不知道解释正不正确,有兴趣深究的可以研究下反汇编代码
而对A a=f(),则转化成了A b;f(b);

  • 优化

一是在使用者层面做优化,二是在编译层在做优化。主要讨论NRV。下面的优化过程就是NRV

X bar()
{
	X xx;
	//.....处理 xx
	return xx;
}
//转化为
void bar(X & _result)
{
	//default constructor被调用
	//C++伪码
	_result.X::x();
	//......直接处理 _result
	return;
}
书上说NRV优化需要一个copy constructor才能激活。对这个我至今无法理解:第一,如果没有copy constructor,
而编译器优化需要,那么编译器可以为我们造一个,不管是trival还是nontrival,因此,优化并不需要我们自己
提供explict copy constructor;第二NRV以后,根本就用不着copy constructor,那还造出来干什么?

这条规则可能适用于作者的cfront编译器,而其具体实现细节不详。

作者说:NRV优化如今被视为标准C++编译器的一个义不容辞的优化操作,我认为不正确,在VS2008上运行如下代码

#include <cstdio>
using namespace std;
class A
{
public:
	A()
	{
		printf("A()\n");
	}
	A(const A &rhs)
	{
		a=rhs.a;
		printf("A(A)!\n");
	}
	A operator=(const A &rhs)
	{
		this->a=rhs.a;
		printf("A=A\n");
		return *this;
	}
private:
	int a;
};


A f()
{
	A a;
	return a;
}


int main()
{
	A a;
	printf("\n");
	f();
	printf("\n");
	a=f();
	printf("\n");
	A b=f();
	printf("\n");
	A c(f());
	printf("\n");
	A d;
	d=f();
}
输出:
/*
A()

A()
A(A)!

A()
A(A)!
A=A
A(A)!

A()
A(A)!

A()
A(A)!

A()
A()
A(A)!
A=A
A(A)!
*/

发现并没有做NVR优化(如果有NVR优化,则不会输出A(A))。另外,值得一提的是A b=f()与A b(f())的输出效果相等,效率很高!可见编译器虽然没有做NVR优化(否则不会输出A(A)),但将其A b=f()转化成了A b;f(b);导致operator=并没有调用!
最后两行代码A d;d=f();居然执行了两次默认构造,两次复制构造与一次赋值!!!这也说明了VS2008的编译器中,初始化语句A a=f() 比A a;a=f()速度要快很多!前者进行了优化使之变成了A a(f())。后者并没有优化!后者先对a初始化,调用默认构造;然后执行f(),调用了一次默认构造与拷贝构造;然后再执行operator =函数,生成另一个__result,再执行拷贝构造!效率何其低下!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值