取一个nontatic data member的地址,得到是member在class布局中的offset;取一个nostatic member function的地址,如果是nonvirtual,则得到他在内存中真正的地址,所有的nonstatic member function都需要对象的地址(this)
指向member function 的指针
double (Point::* pmf)();
定义并初始化:
double(Point::*coord)()=&Point::x;
调用:
(origin.*coord)();
//或者
(ptr->*coord)();
指向member function 的指针的声明语法,以及指向“member selection运算符“的指针,其作用是作为this指针的空间保留着。而static member function没有this指针,因此其类型是函数指针,而不是member function的指针。
对于除了virtual function,多重继承以及virtual base classes的情况,指向member function的指针和指向nonmember function的指针成本相当。
指向virtual member function的指针
虚拟机制支持指向member function的指针,但virtual function的地址在编译期未知,而只能取到其在virtual table中的索引值
class Point
{
public:
virtual ~Point();
float x();
float y();
virtual float z();
}
//声明函数指针
float (Point::*pmf)()=&Point::z;
Point *ptr=new Point;
//则对z()的调用
(ptr->*pmf)();
//会转化为
(*ptr->vptr[(int)pmf])(ptr);
指向函数的指针能够寻址出nonvirtual 和virtual member function,前者代表内存地址,后者代表在virtual table中的索引值。pmf必须能够对上述两种值加以区分。
作者提到一种解决方法,但该种方法必须假设继承体系中最多只有128个virtual functions
(((int)pmf)&127)
? //nonvirtual invocation
(*pmf)(ptr)
: //virtual invocation
(*ptr->vptr[(int)pmf](ptr));
在多重继承之下,指向Member Funcions的指针
struct _mptr{
int delta;
int index;
Union{
ptrtofunc faddr;//nonvirtual member function地址
int v_offset;//virtual table索引
};
};
上述结构用于支持多重继承下指向member function的指针。再该模型下
(ptr->*pmf)();
//转化为
(pmf.index<0)
?//nonvirtual invocation
(*pmf.faddr)(ptr)
://virtual invocation
(*ptr->vptr[pmf.index](ptr));
缺陷:
1、判断成本
2、当传递一个不变值的指针给member function时,需要产生一个临时对象
extern Point3d foo(const Point3d&, Point3d(Point3d::*)());
void bar(const Point3d& p){
Point3d pt = foo(p,&Point3d::normal);
}
//如果&Point3d::normal为
{0,-1,10727417}
//则会产生一个临时性的对象,有明确的初值
//虚拟C++码
_mptr temp={0,-1,10727417};//delta=0,index=-1,faddr=10727417
foo(p,temp);
4-5 Inline Functions内联函数
关键词inline知识一项请求,如果这项请求被接受(在某个层次上,其执行成本比一般的函数调用及返回机制所带来的负荷低),编译器就必须认为他可以用一个表达式合理地将这个函数扩展开来。
处理过程:
1.分析函数定义,以决定函数的“intrinsic inline ability”。如果函数太复杂,被判断为不可称为inline,会被转为一个static函数。
2.在调用处将该inline函数扩展。
注:1 : inline的declaration和definition一定要在同一个档案里面
2 : class的话,member function可以implicit inline,有无template都一样
3 : 一般function,要inline一定得在function的declaration前面加上"inline"的关键字,一般function不会implicit inline。
4 : implicity inline的话,你可以在class内的declaration宣告inline或在class外的definition宣告,也可以同时宣告
5:,如果一个inline函数会在多个源文件中被用到,那么必须把它定义在头文件中
形式参数
#define和inline的区别在于#define使用预编译器,没有使用堆栈,所以只是预编译器符号表上的简单替换,不能进行参数有效性检测以及使用C++类的成员访问控制。
inline代码放入预编译符号表中,高效;但它是个真正的函数,调用时有严格的参数检测,可以作为类的成员函数
替换时间:宏函数是在预编译阶段执行代码替换,inline函数是在编译的过程中执行代码替换
替换性质,宏定义的替换仅仅是文本替换,inline函数是真正意义上的代码替换
注:在调用时,如果参数是一个表达式,宏定义进行简单的表达式替换;内敛函数先求表达式的值,再传递给函数。
由于在inline的每个调用处会直接展开,对于实参的多次求值操作可能会多次引入临时性对象。为避免这种情况,在替换之前完成求值操作。
inline int bar()
{
int minval;
int val1=1024;
int val2=2048;
/*(1)*/ minval = min(val1,val2);
/*(2)*/ minval = min(1024,2048);
/*(3)*/ minval = min(foo(),bar()+1);
return minval;
}
扩展情况如下
//(1)参数直接代换
minval = val1 < val2 ? val1 : val2;
//(2)代换之后直接使用常量
minval=1024;
//(3)有副作用,所以导入临时对象,这样可以避免每次都重新计算
int t1;
int t2;
minval = (t1=foo()),(t2=bar()+1),t1<t2?t1:t2;
局部变量
inline函数中的每一个局部变量都必须被放在函数调用的一个封闭区段中,拥有独一无二的名称,如果inline函数以单一的表达式扩展多次,那么每次扩展都需要自己的一组局部变量。如果inline函数以分离的多个式子被扩展多次,则只需要一组局部变量,就可以重复使用(因为他们被放在一个封闭区段中,有自己的scope)。
//单一表达式
minval = min (val1, val2)+min (foo(),foo()+1);
//分离的多个式子
minval1=min(val1, val2);
minval2=min(foo(),foo()+1);
minval=minval1+minval2;
inline可能导致大量剧对象的产生。
一个inline如果被调用太多次,会产生大量的扩展码,使程序的大小暴涨。
inline中再有inline,可能会使得一个表面上看起来平凡的inline因其连锁复杂度而没办法扩展开(复杂class体系下的constructor...)。