c++ 对象函数
member functions的调用方式
- c++支持三种类型的member functions:static、nonstatic、virtual,且每一种调用方式不尽相同
nonstatic member functions
- nonstatic member function至少和nonmember function有着相同的效率
现有如下函数调用:
float do(const A *_this) {...}
float A::do() const {...}
//第一个函数转换
float do(const A *_this)
{
return sqrt(
_this->x * _this->x + _this->y * _this->y + _this->z * _this->z
)
};
//第二个nonstatic member function转换成上面相同的形式nonmember function
-
nonstatic member function转化为 nonmember function步骤:
-
改写函数原型以安插一个额外参数到member function,将提供class object存取渠道,此参数也就是this指针。若member function为const,this也需加个const
float do(const A* const _this)
-
将每个对nonstatic data member的存取操作改为this指针
_this->x * _this->x + _this->y * _this->y + _this->z * _this->z
-
将member function经过mangling处理重新写为一个外部函数,且名称是独一无二的
extern do_7AFv( A* const this );
-
-
一般而言,data member的名称前会加上class名称,形成独一无二的命名;而member function则还需加上参数链表
class B { public: int val; ... }; //对val进行name mangling val_3; ----------------------- class C { public: void x(float newX); float x(); } //member function进行name mangling void x__5CFf(float newX); float x__5CFv();
virtual member functions
现有如下代码:
class A
{
virtual A do1() const;
virtual float do2() const;
}
A a;
A* pt = &a;
pt->do1();
//转化
( *pt->vptr[1] )(pt);
float d = do2();
//转化
float d = ( *this->vptr[2] )( this );
a.do1();
//转化
( *a.vptr[1] )( &a );
( * pt -> vptr [1] )( pt )其中:1为virtual table slot的索引值,关联到virtual member function,也就是do1()
( *a.vptr[1] )( &a ) 没有必要,应该这样调用:A::do1()
-
经由class object调用virtual function优化跟nonstatic member function一样
-
为支持virtual function机制,需要能对多态对象进行执行期类型判断,将必要信息加在指针或引用上。而必要信息则有:
- 指针或引用指向的对象的地址
- 对象类型的结构的地址
-
多态表示用一个public base class的指针或引用寻址一个derived class object
Point* ptr; ptr = new Point2d;
-
当前ptr被称为消极的多态形式,可以在编译时期完成(virtual base class除外);当ptr指出的具体对象被使用时才是积极的
ptr->z();
-
class是否支持多态,唯一方法是看其是否有virtual function
-
-
实现多态。我们需要在每个class object上增加两个members:
- 一个字符串或数字,表示class类型
- 一个指针,指向一表格,表格中含有virtual functions执行期地址
-
随后只需两步即可找到其地址:
- 每个class object安插一个由编译器生成的指针,该指针指向表格
- 每个virtual function被指派一个表格索引值
-
一个class只有一个virtual table,每个table内含对应的class object中的active virtual functions实例地址。而active virtual functions又包括:
- 这一class定义的函数实例
- 继承自base class的函数实例
- 一个pure virtual called函数
-
对于多重继承,销毁对象时若调用delete需要指向derived class object的起始处,但因为指针或引用真正所指的对象在执行期才可以确定,所以offset无法在编译期求得。针对多重继承这种情况,derived class需要内含n-1个额外的virtual tables(n表示其上一层base classes个数)。那么如何支持一个class拥有多个virtual tables呢?只需将每一个tables以外部对象的形式产出,并赋予独一无二的名称
-
不要在virtual base class中声明nonstatic data members
static member function
- static member function特性:
- 没有this指针,成为一个callback函数
- 不可直接存取class中的nonstatic members
- 不可被声明为const、volatile、virtual
- 不需要经由class object调用
- static member function同样也有name mangling
- 对static member function取地址,得到的是内存中的地址;由于没有this指针,static member function地址类型是一个nonmember函数指针
指向member functions 的指针
-
指向member function的指针和指向member selection operator的指针,其作用是作为this指针的空间保留者。这也说明了为什么static member function的指针类型是函数指针,毕竟其没有this指针
-
使用member function指针,若不用于virtual function、多重virtual继承、virtual base class,其成本跟用nomember function指针差不多
-
virtual member function的地址在编译期是未知的,我们所能知道的仅是virtual function在其相关之virtual table的索引值
inline functions
-
inline关键词只是一个请求,若此请求被编译期接受,编译期则认为其可以用一个表达式将函数展开
-
inline函数的复杂度通过计算assignments、function calls、virtual function calls等操作的次数以及每个表达式种类的权值综合决定
-
若函数因其复杂度或建构问题,被判断不可称为Inline,那么此函数将被转换为static函数,并在”被编译模块”内产生对应的函数定义
-
inline function展开期间,做了以下三件事:
-
每个形参都被对应的实参取代。但这其中可能会导致实际参数的多次求值,面对这种情况,需要引入临时对象。比如,若实际参数是常量表达式,在替换前先引入临时对象,常量表达式求值后赋值给临时对象,后继inline替换只需使用临时对象
inline int min( int i, int j ) { return i < j ? i : j; } inline int bar() { int minval; minval = min( foo(), bar() + 1 ); return minval; } //minval = min( foo(), bar() + 1 )展开 int t1, t2; minval = ( t1 = foo() ), ( t2 = bar() + 1 ), t1 < t2 ? t1 : t2;
-
若内含局部变量,则需将局部变量放在函数调用的一个封闭区段,且拥有一个独一无二的名称。因为,如果Inline以单一表达式的方式扩展多次,每次扩展都需要自己的局部变量,特别是还含有副作用参数,可能会导致大量临时性对象产生;但如果是分离成多个式子扩展多次,只需一组局部变量即可重复使用
inline int min( int i, int j ) { int minval = i < j ? i : j; //局部变量 return minval; } { ... //minval = min(val1, val2); int __min_lv_minval; minval = (__min_lv_minval = val1 < val2 ? val1 : val2), __min_lv_minval; }
-
inline函数提供强有力的工具,但于noninline函数相比还是需要更小心地处理
-
-
尽量不要Inline中套inline,可能会使简单的Inline因其连锁复杂度而没办法展开