More Effective C++ (效率)

6.4:理解临时对象

<1>为了使函数成功调用而进行隐式类型转换和函数返回对象时

<2>当传送给函数的对象类型与参数类型不匹配时会产生的一种情况

<3>当通过传值方式传递对象或者传递常量引用参数时,才会发生类型转换

示例代码如下:

 1 #include<iostream>
 2 using  namespace std;
 3 class  Test
 4 {
 5     int a;
 6 public:
 7     Test(int data = 10):a(data)
 8     {
 9         cout<<"Construction :"<<this<<endl;
10     }
11     ~Test()
12     {
13         cout<<"Destroy :"<<this<<endl;
14     }
15     void Print() const
16     {
17         cout<<"data:"<<a<<endl;
18     }
19 };
20 void Fun1(const Test & a)
21 {
22     a.Print();
23 }
24 void Fun2(Test &b)
25 {
26     b.Print();
27 }
28 void  Fun3(Test c)
29 {
30     c.Print();
31 }
32 void  main()
33 {
34     Fun1(10);   //传递常量引用参数
35 //  Fun2(10);   //error!!  //非常引用
36     Fun3(20);   //传值方式传递对象
37 }
38 /*
39 Construction :0012FE88
40 data:10
41 Destroy :0012FE88
42 Construction :0012FE6C
43 data:20
44 Destroy :0012FE6C
45 */

6.9:理解虚拟函数、多继承、虚基类和RTTI所需的代价

<1>virtual table只实现了虚拟函数的一半机制,仅仅只有这些是没有用的。

只有用某种方法指出每个对象对应的vtbl时,它们才能使用。其实这也是virtual table pointer 的工作,它来建立这种联系。

每个声明了虚函数的对象都带有它,它是一个看不见的数据成员,指向对应类的virtual table 。

这个看不见的数据成员也称为vptr,被编译器加在对象里,位置只有编译器才知道。

从理论上讲,我们可以认为包含有虚函数的对象的布局是这样的:

假如,我们有一个程序,包含几个C1和C2对象。对象,vptr和刚刚我们讲述的vtabl之间的关系,在程序里我们可以这样去想象:

下面考虑这段代码的执行:

1 void makeACall(C1 *pC1)
2 {
3     pC1->f1();
4 }

通过指针 pC1调用虚拟函数f1。

仅仅看这段代码,你不会知道它调用的是那一个f1 函数?? C1::f1 或C2::f1,因为pC1可以指向C1对象也可以指向C2对象。

尽管如此编译器仍然得为在makeACall中的f1 函数的调用生成代码,它必须确保无论 pC1指向什么对象,函数的调用必须正确。

编译器生成的代码会做如下这些事情:

1:通过对象的 vptr 找到类的 vtbl。

这是一个简单的操作,因为编译器知道在对象内哪里能找到 vptr(毕竟是由编译器放置的它们) 。

因此这个代价只是一个偏移调整(以得到vptr)和一个指针的间接寻址(以得到 vtbl)。 

2:找到对应vtbl内的指向被调用函数的指针(在上例中是f1) 。

这也是很简单的,因为编译器为每个虚函数在vtbl 内分配了一个唯一的索引。这步的代价只是在vtbl 数组内的一个偏移。 

3: 调用第二步找到的的指针所指向的函数。

如果我们假设每个对象有一个隐藏的数据叫做vptr,而且f1 在 vtbl 中的索引为i,此语句

pC1->f1();

生成的代码就是这样的:

 (*pC1->vptr[i])(pC1);    //调用被vtbl中第i个单元指向的函数,而pC1->vptr指向的是vtbl;pC1被作为this指针传递给函数。

4:实际上,虚函数不能是内联函数。

因为“内联”是指“在编译期间用被调用的函数体本身来代替函数调用的指令。”而虚函数的“虚”是指“直到运行时才能知道要调用的是哪一个函数。”

如果编译器在某个函数的调用点不知道具体是哪个函数被调用,你就能知道为什么它不会内联该函数的调用。

这是虚函数所需的第三个代价:你实际上放弃了使用内联函数。(当通过对象调用虚函数时,它可以被内联,

但是大多数虚函数是通过对象的指针或引用被调用的,这种调用不能被内联。因为这种调用是标准的调用方式,所以虚函数实际上不能被内联。)

<2>虚基类

多继承经常导致对虚基类的需求。没有虚基类,如果一个派生类有一个以上从基类的继承路径,基类的数据成员被复制到每一个继承类对象里,继承类与基类间的每条路径都有一个拷贝。

程序员一般不会希望发生这种复制,而把基类定义为虚基类则可以消除这种复制。

下面考虑这幅图:所谓的菱形继承

这也就是虚基类产生的原因。

<3>运行时类型识别

RTTI 能让我们在运行时找到对象和类的有关信息,所以肯定有某个地方存储了这些信息让我们查询。

这些信息被存储在类型为 type_info 的对象里,你能通过使用typeid操作符访问一个类的type_info 对象。

在每个类中仅仅需要一个RTTI的拷贝,但是必须有办法得到任何对象的类型信息。实际上这叙述得不是很准确。

语言规范上这样描述:我们保证可以获得一个对象动态类型信息,如果该类型有至少一个虚函数。

这使得RTTI数据似乎有些象virtual function talbe(虚函数表)。每个类我们只需要信息的一个拷贝,我们需要一种方法从任何包含虚函数的对象里获得合适的信息。

这种 RTTI 和virtual function table 之间的相似点并不是巧合:RTTI被设计为在类的vtbl基础上实现。

例如,vtbl数组的索引 0 处可以包含一个 type_info 对象的指针,这个对象属于该vtbl相对应的类。上述C1 类的vtbl看上去象这样:

Good Good Study, Day Day Up.

顺序  选择  循环  坚持

作者:kaizen
声明:本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此声明,且在文章明显位置给出本文链接,否则保留追究法律责任的权利。
签名:顺序 选择 循环
原文地址:https://www.cnblogs.com/Braveliu/p/2847684.html