C++静态成员函数不能声明为const、volatile、virtual的原因 与 C++的对象模型

使用visual studio命令提示查看C++内存布局

http://blog.csdn.net/daydreamingboy/article/details/8982563
对于学习内存布局而言,学会了这个工具,就好像学算术的人会使用了计算器。另外,gcc/g++的话,
可用-fdump-class-hierarchy选项。


来自对《深度探索C++对象模型 侯捷译》的一点总结、体会。

Only one reason,静态成员函数没有this指针,不与类的实例(对象)“挂钩”。

那么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(); 
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是一样的,不再重复。

5) static

class A
static void foo(){}
] bar;

A::foo(); // 调用static成员函数无需对象

bar.foo(); //被编译器转化成 A::foo(); 

static成员函数没有this指针,所以只能访问static成员(属于类的),不能直接访问non static成员(属于对象的)。

#include <iostream>
#include <thread>
using namespace std;

class Foo{
public:
	void bar( int i ) {
		cout << "id: " << i << endl;
	};
};

int main(int argc, char* argv[])
{
	Foo a;
	
	thread t(&Foo::bar, &a, 1); // 对比编辑器对类的成员函数的处理
	
	t.join();

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值