//来自对《深度探索C++对象模型 侯捷译》的一点总结、体会。
静态成员函数没有this指针,不与类的实例(对象)“挂钩”,所以C++静态成员函数不能声明为const、volatile、virtual。那么const/ volatile /virtual这些限定符是如何通过this指针发挥功能的呢?
【1】基础:何为this指针?它有什么用?
this指针指向类的某个实例(对象),叫它当前对象。
①举例说明:
class A
{
int i;
void foo ( ){
i++;
}
};
void A::foo ( ); 会被编译器转化为一个外部的、非成员的、普通的函数:(实际的函数名还会包含类名与形参的编码,以区分不同的类和重载,但这里为了简单仍用foo)。
void foo ( A * this ){ // 严格的形式应是A * const this
(this->i)++;
}
相应地,
A bar;
bar.foo(); 会被编译器转换为:foo( &bar ); // 没考虑对函数名的特殊处理(见上),下同。
②拓展,另一个带返回值的函数例子:
class A
{
int i;
int foo ( ){
i++;
return i;
}
}bar ;
//编译器转化后如下
void foo (A * this, int& result){
(this->i)++;
result = this->i;
return;
}
int a = bar.foo(); 会变成 foo( &bar, a);如果直接调用bar.foo(),会有临时变量,不考虑代码优化。
“返回值为引用类型”跟“返回值为指针类型”的道理是一样的,引用在必要的时候是通过指针(常量指针 type * const,所以引用必须初始化,而且自始至终都代表那一个变量 ) 实现的,二者在编译器的汇编(机器指令)实现机制是一样的。“不要返回局部对象的指针(地址值)”,同理不要返回局部对象的引用。C++是面向对象的高级语言,however,本书的作者Lippman认为了解C++的实现机理对于中高级C++程序员来说是必需的。如果不知道编译器对你的代码做了什么,那么代码的效率永远都是未知数。嵌入式、硬件开发的厉害C程序员看一眼C代码,甚至都知道它在目标平台上的汇编是什么样的。这种C语言与汇编的关系,使得C语言适合某些嵌入式、驱动、操作系统等底层开发。虽然C++的设计者想在语言层面对程序员做透明化处理(比如引用),但是面对以“保证效率和兼容C的情况下提供OO”为设计目标的C++,程序员还是了解一下底层为好。
【2】多态与虚函数(C++ 的多态离不开 虚函数 和 指针(或引用),缺一不可)
class Base
{
virtual void foo () {}
};
class Derived1: public Base
{
void foo () {}
};
class Derived2: public Base
{
void foo () {}
};
Base b;
Derived1 d1;
Derived2 d2;
Base * ptr = b; ptr->foo(); // 调用的是Base的
ptr = d1; ptr->foo(); // 调用的是Derived1的
ptr = d2; ptr->foo(); // 调用的是Derived2的
b.foo(); // 调用的是Base的
d1.foo(); // 调用的是Derived1的
d2.foo(); // 调用的是Derived2的
但是:
b = d1; b.foo();// 调用的是Base的,因为b = d1;发生了“截断”(sliced )
b = d2; b.foo();// 调用的是Base的,因为b = d2;发生了“截断”(sliced )
一个类如果含有虚函数,那么这个类的每个对象都会额外拥有一个编译器添加的指针(虚表指针 vptr),指向一个本类的对象所共享的一维数组(虚函数表),虚函数的地址就存放在一维数组的项(slot 槽)中(此外虚函数表还存放了类型信息,位于最开头)。【不考虑多重继承(乃至虚拟继承)这些复杂的境况,很多时候该用组合,而非继承。】仅就这种机制,父类、子类对象的不同之处在于虚函数表里存放的地址不同(类型信息也不同)。而虚函数表,一个类准备一个就好了。
ptr->foo();会被编译器转化成:foo(ptr); *( (ptr->vptr)[ 1 ] ) (ptr); // foo在虚函数表中的索引是1
这样,若ptr指向的是父类对象,则ptr->vptr指向父类的虚函数表,从而调用的是父类的foo函数;若ptr指向的是子类对象,则ptr->vptr指向该子类的虚函数表,从而调用的是该子类的foo函数;这样就实现了多态。
在执行b = d1; 时,调用copy assignment operator (拷贝赋值运算符),d1的vptr并不会赋值给b的vptr,b的vptr维持原值不变,仍然指向Base的虚函数表。
【3】const对象与const成员函数
class A
{
void foo1(){}
void foo2() const{}
};
const A a;
a.foo1(); //error
a.foo2(); // ok
const成员函数其实就是用了const A * this指针:
void foo2() const{}会被编译器转化为 void foo2(const A * this){} // 严格的说是const A * const this(“只读”指针与常量指针的话题这里就不说了)这样const成员函数就不能修改对象,从而const对象只调用const成员函数,不能调用非const成员函数。
【4】volatile对象与volatile成员函数
道理与const是一样的,不再重复。将成员函数声明为 volatile 。如果一个类对象的值可能被修改的方式是编译器无法控制或检测的,则把它声明为 volatile 。与 const 类对象类似,对于一个 volatile 类对象,只有 volatile 成员函数、构造函数、析构函数可以被调用。
【5】static
class A
{
static void foo(){}
} bar;
A::foo(); // 调用static成员函数无需对象
bar.foo(); //被编译器转化成 A::foo();
static成员函数没有this指针,所以只能访问static成员(属于类的),不能直接访问non static成员(属于对象的)。