您的位置:首页 > 编程语言 > C语言/C++

由一道题目想到的C++编译器优化问题

2011-11-14 09:27 288 查看
这两天看到了一个问题,看似简单,但是用的知识着实不少,原题如下:

#include "stdafx.h"

class Base
{
public:
Base(){}
virtual ~Base(){}
Base(const Base &other);            // 只声明, 没定义
private:
Base &operator=(const Base &other);
} ;

int _tmain(int argc, _TCHAR* argv[])
{
const Base &b = Base() ;        // 为什么没有导致链接错误? 应该调用拷贝构造函数才对, 然而我只声明没定义!

return 0;
}


我一开始想到的就是编译器优化了:推测下编译器应该做了优化: const Base &b = Base() ;这么写的话,按照语义是:

1.调用Base的构造函数

2.调用Base的赋值函数 b=临时对象但是编译器大概认为没必要这么两步走,直接调用了Base的构造函数。。。。

后面经过大侠A的指导,指出问题所在:

Base b = a 的时候, b 还没有构造, 所以需要先对 b 进行构造, 再紧接着进行赋值, 你说 c++ 编译器的设计者, 能不优化一下么? 所以在所有 c++ 规定了这个行为, 即 Base b = a 就是Base
b(a)
, 这也是 c++ 语法的 sweet.

恩。。。。那么暂时我们就认为const Base &b = Base() ; 被编译器变成了Base b(Base()),那么还是应该会调用默认构造函数和拷贝构造函数啊,可以明明程序并没有报错!!!

关键人物大侠B出现了:

个人以为,const Base &b = Base() ; 引用是直接绑定在右边的那个匿名对象上的,所以木有发生拷贝构造,所以跟拷贝构造函数是否定义木有关系。
参考 ISO/IEC 14882:2003(E)
8.5.3 References
第4 5条款
— Otherwise, the reference shall be to a non-volatile const type (i.e., cv1 shall be const).
[Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
int i = 2;
double& rd3 = i; // error: type mismatch and reference not const
—end example]
— If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound in one of the following ways (the choice is implementation-defined):
— The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.
— A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.93)


看着这么一大堆英文又迷糊了。。。。静下心来好好读了下:

关键在于这段话

— If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound in one of the following ways (the choice is implementation-defined)://如果初始化表达的右边是个类类型,并且左边是一个对右值的常量引用,那么这种情况呢,可以由编译器用以下两种方式实现(两者选一):
— The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.//直接将引用绑定到右值
— A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.//临时对象被创建,并且调用拷贝构造函数将临时对象拷贝到右值,再将引用绑定到临时对象。

显然我测试到的VC、GCC都选择了第一种做法直接将引用绑定到右值。。。。~

关于引用的几点说明,摘自于网络:/article/1773865.html

/*
(1)&在此不是求地址运算,而是起标识作用。

(2)类型标识符是指目标变量的类型。

(3)声明引用时,必须同时对其进行初始化。

(4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。

ra=1; 等价于 a=1;

(5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,
它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。
故:对引用求地址,就是对目标变量求地址。&ra与&a相等。

(6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。
*/


引用作为返回值,必须遵守以下规则:

  (1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

  (2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

  (3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

  (4)引用与一些操作符的重载:

  流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: