《深度摸索C++工具模子》念书条记(4)
当前位置:以往代写 > C/C++ 教程 >《深度摸索C++工具模子》念书条记(4)
2019-06-13

《深度摸索C++工具模子》念书条记(4)

《深度摸索C++工具模子》念书条记(4)

副标题#e#

***非静态成员函数(Nonstatic Member Functions)***

C++的设计准则之一就是: nonstatic member function至少必需和一般的nonmember function有沟通的效率。也就是说,假如我们 要在以下两个函数之间作选择:

float magnitude3d(const Point3d *this) { ... }
float Point3d::magnitude3d() const { ... }

那么选择member function不该该带来 什么特别承担。因为编译器内部已将“member函数实体”转化为对等的“nonmember函 数实体”。下面是magnitude()的一个nonmember界说:

float Pointer3d::magnitude() const
{
return sqrt(_x*_x + _y*_y + _z*_z);
}
// 内部转化为
float magnitude_7Point3dFv(const Point3d *this)  //已对函数名称举办 “mangling”处理惩罚
{
return sqrt(this->_x*this->_x + this- >_y*this->_y + this->_z*this->_z);
}

此刻,对该函数的每一个调 用操纵也都必需转换:

obj.magnitude();
// 转换为
magnitude_7Point3dFv (&obj);

对付class中的memeber,只需在member的名称中加上class名称,即可形成 唯一无二的定名。但由于member function可以被重载化,所以需要更遍及的mangling手法,以提供绝对 唯一无二的名称。个中一种做法就是将它们的参数链表中各参数的范例也编码进去。

class Point {
public:
void x(float newX);
float x();
...
};
// 内部转化为
class Point {
void x_5PointFf(float newX);  // F暗示function,f暗示其第一个参数范例是float
float x_5PointFv();  // v暗示其没有参数
};

上述的mangling手法可在链接时期查抄出任何不正确的挪用操纵,但由于编码 时未思量返回范例,故假如返回范例声明错误,就无法查抄出来。

***虚拟成员函数(Virtual Member Functions)***

对付那些不支持多态的工具,经过一个class object挪用一个virtual function,这种操纵应该老是被编译器像看待一般的nonstatic member function一样地加以决策:

// Point3d obj
obj.normalize();
// 不会转化为
(*obj.vptr[1]) (&obj);
// 而会被转化未
normalize_7Point3dFv(&obj);

***静态 成员函数(Static Member Functions)***

在引入static member functions之前,C++要求所有 的member functions都必需经过该class的object来挪用。而实际上,假如没有任何一个nonstatic data members被直接存取,事实上就没有须要通过一个class object来挪用一个member function.这样一来便 发生了一个抵牾:一方面,将static data member声明为nonpublic是一种好的习惯,但这也要求其必需 提供一个或多个member functions来存取该member;另一方面,固然你可以不靠class object来存取一 个static member,但其存取函数却得绑定于class object之上。

static member functions正是 在这种景象下应运而生的。


#p#副标题#e#

编译器的开拓者针对static member functions,别离从编译层面和 语言层面临其举办了支持:

(1)编译层面:当class设计者但愿支持“没有class object 存在”的环境时,可把0强制转型为一个class指针,因而提供出一个this指针实体:

// 函数挪用的内部转换
object_count((Point3d*)0);

(2)语言层 面:static member function的最大特点是没有this指针,假如取一个static member function的地点 ,得到的将是其在内存中的位置,其地点范例并不是一个“指向class member function的指针 ”,而是一个“nonmember函数指针”:

unsigned int Point3d::object_count() { return _object_count; }
&Point3d::object_count();
// 会获得一个地点,其范例不是
unsigned int (Point3d::*)();
// 而是
unsigned int (*)();

static member function常常被用作回调(callback)函数。

***虚拟成员函数(Virtual Member Functions)***

对付像ptr->z()的挪用操纵将 需要ptr在执行期的某些相关信息,为了使得其能在执行期顺利高效地找到并挪用z()的适当实体,我 们思量往工具中添加一些特别信息。

(1)一个字符串或数字,暗示class的范例;

(2) 一个指针,指向某表格,表格中带有措施的virtual functions的执行期地点;

在C++中, virtual functions可在编译时期获知,由于措施执行时,表格的巨细和内容都不会改变,所以该表格的 建构和存取皆可由编译器完全把握,不需要执行期的任何参与。

(3)为了找到表格,每一个 class object被安插上一个由编译器内部发生的指针,指向该表格;

(4)为了找到函数地点, 每一个virtual function被指派一个表格索引值。

一个class只会有一个virtual table,个中内 含其对应的class object中所有active virtual functions函数实体的地点,详细包罗:

(a) 这个class所界说的函数实体

它会改写一个大概存在的base class virtual function函数实体。 若base class中不存在相应的函数,则会在derived class的virtual table增加相应的slot.

(b )担任自base class的函数实体

#p#分页标题#e#

这是在derived class抉择不改写virtual function时才会呈现 的环境。详细来说,base class中的函数实体的地点会被拷贝到derived class的virtual table相对应 的slot之中。

(c)pure_virtual_called函数实体

对付这样的式子:

ptr ->z();

运用了上述手法后,固然我不知道哪一个z()函数实体会被挪用,但却知道 每一个z()函数都被放在slot 4(这里假设base class中z()是第四个声明的virtual function)。

// 内部转化为
(*ptr->vptr[4])(ptr);

***多重担任下的 Virtual Functions***

在多重担任中支持virtual functions,其巨大度环绕在第二个及后继的 base classes身上,以及“必需在执行期调解this指针”这一点。

#p#副标题#e#

多重担任到来的问题:

(1)经过指向“第二或后继之base class”的指针(或reference)来挪用 derived class virtual function,该挪用操纵连带的“须要的this指针调解”操纵,必需 在执行期完成;

以下面的担任体系为例:

class Base1 {
public:
Base1();
virtual ~Base1();
virtual void speakClearly();
virtual Base1 *clone() const;
protected:
float data_Base1;
};

class Base2 {
public:
Base2();
virtual ~Base2();
virtual void mumble();
virtual Base2 *clone() const;
protected:
float data_Base2;
};

class Derived : public Base1, public Base2 {
public:
Derived();
virtual ~Derived ();
virtual Derived *clone() const;
protected:
float data_Derived;
};

对付下面一行:

Base2 *pbase2 = new Derived;

会被 内部转化为:

// 转移以支持第二个base class
Derived *temp = new Derived;
Base2 *pbase2 = temp ? temp + sizeof(Base1) : 0;

假如没有这样的调解,指针的 任何“非多态运用”都将失败:

pbase2->data_Base2;

当程 序员要删除pbase2所指的工具时:

// 必需挪用正确的virtual destructor函数实体
// pbase2需要调解,以指出完整工具的起始点
delete pbase2;

指针必需被再一 次调解,以求再一次指向Derived工具的起始处。然而上述的offset加法却不可以或许在编译时期直接设定, 因为pbase2所指的真正工具只有在执行期才气确定。

自此,我们大白了在多重担任下所面对的独 特问题:经过指向“第二或后继之base class”的指针(或reference)来挪用derived class virtual function,该挪用操纵所连带的“须要的this指针调解”操纵,必需在执行 期完成。有两种要领来办理这个问题:

(a)将virtual table加大,每一个virtual table slot 不再只是一个指针,而是一个聚合体,内含大概的offset以及地点。这样一来,virtual function的调 用操纵产生改变:

(*pbase2->vptr[1])(pbase2);
// 改变为
(*pbase2- >vptr[1].faddr)(pbase2 + pbase2->vptr[1].offset);

这个做法的缺点是,它相 当于连带惩罚了所有的virtual function挪用操纵,不管它们是否需要offset的调解。

(b)利 用所谓的thunk(一小段assembly码),其做了以下两方面事情:

(1)以适当的offset值调解 this指针;

(2)跳到virtual function去。

pbase2_dtor_thunk:
this += sizeof(base1);
Derived::~Derived(this);

#p#副标题#e#

Thunk技能答允virtual table slot继承内含一个简朴的指针,slot中的地点可以直接指向virtual function,也可以指向一个相关的thunk. 于是,对付那些不需要调解this指针的virtual function而言,也就不需要承载效率上的特别承担。

(2)由于两种差异的大概:

(a)经过derived class(或第一个base class)挪用;

(b)经过第二个(或其后继)base class挪用,同一函数在virtual table中大概需要多笔对应 的slot;

Base1 *pbase1 = new Derived;
Base2 *pbase2 = new Derived;

delete pbase1;
delete pbase2;

固然两个delete操纵导致沟通的Derived destructor,但它们需要两个差异的virtual table slots:

(a)pbase1不需要调解this指针, 其virtual table slot需安排真正的destructor地点

(b)pbase2需要调解this指针,其virtual table slot需要相关的thunk地点

详细的办理要领是:

#p#分页标题#e#

在多重担任下,一个derived class内含n-1个特另外virtual tables,n暗示其上一层base classes的数目。按此手法,Derived将内 含以下两个tables:vtbl_Derived和vtbl_Base2_Derived.

(3)答允一个virtual function的返 回值范例有所变革,大概是base type,大概是publicly derived type,这一点可以通过Derived:: clone()函数实体来说明。

Base2 *pb1 = new Derived;

// 挪用 Derived::clone()
// 返回值必需被调解,以指向Base2 subobject
Base2 *pb2 = pb1- >clone();

当运行pb1->clone()时,pb1会被调解指向Derived工具的起始地点, 于是clone()的Derived版会被挪用:它会传回一个指针,指向一个新的Derived工具;该工具的地点在 被指定给pb2之前,必需先颠末调解,以指向Base2 subobject.当函数被认为“足够小”的时 候,Sun编译器会提供一个所谓的“split functions”技能:以沟通算法发生出两个函数, 个中第二个在返回之前,为指针加上须要的offset,于是无论通过Base1指针或Derived指针挪用函数, 都不需要调解返回值;而通过Base2指针所挪用的,是另一个函数。

***虚拟担任下的Virtual Functions***

其内部机制实在过分诡异迷离,故在此略过。独一的发起是:不要在一个virtual base class中声明nonstatic data members.

***函数的效能***

由于nonmember、static member和nonstatic member函数都被转化为完全沟通的形式,故三者的效率安详沟通。virtual member 的效率明明低于前三者,其原因有两个方面:(a)结构函数中对vptr的设定操纵;(b)偏移差值模子 。

***指向Member Function的指针***

取一个nonstatic member function的地点,假如 该函数是nonvirtual,则获得的功效是它在内存中真正的地点。

我们可以这样界说并初始化该指 针:

double (Point::*coord)() = &Point::x;

想挪用它,可以这么 做:

(origin.*coord)();
 (ptr->*coord)();

“指向 Virtual Member Functions”之指针将会带来新的问题,请留意下面的措施片断:

float (Point::*pmf)() = &Point::z;
Point *ptr = new Point3d;

个中,pmf是一个指向member function的指针,被设值为Point::z()(一 个virtual function)的地点,ptr则被指向一个Point3d工具。

假如我们直接经过ptr挪用z() :

ptr->z();  // 挪用的是Point3d::z()

但假如我们经过pmf间接调 用z():

(ptr->*pmf)();  // 仍然挪用的是Point3d::z()

也就是说 ,虚拟机制仍然可以或许在利用“指向member function之指针”的环境下运行,但问题是如何实 现呢?

#p#副标题#e#

对一个nonstatic member function取其地点,将得到该函数在内存中的地点;而对一个 virtual member function取其地点,所能得到的只是virtual function在其相关之virtual table中的 索引值。因此通过pmf来挪用z(),会被内部转化为以下形式:

(*ptr->vptr[(int) pmf])(ptr);

可是我们如何来判定传给pmf的函数指针指向的是内存地点照旧virtual table中的索引值呢?譬喻以下两个函数都可指定给pmf:

// 二者都可以指定给pmf
float Point::x() { return _x; }  // nonvirtual函数,代表内存地点
float Point::z() { return 0; }  // virtual函数,代表virtual table中的索引值

cfront 2.0是通过判定 该值的巨细举办判定的(这种实现能力必需假设担任体系中最多只有128个virtual functions)。

为了让指向member functions的指针也可以或许支持多重担任和虚拟担任,Stroustrup设计了下面一 个布局体:

// 用以支持在多重担任之下指向member functions的指针
struct _mptr {
int delta;
int index;
union {
ptrtofunc faddr;
int v_offset;
};
};

个中,index暗示virtual table索引,faddr暗示 nonvirtual member function地点(当index不指向virtual table时,被设为-1)。

在该模子之 下,以下挪用操纵会被转化为:

(ptr->*pmf)();
// 内部转化为
(pmf.index < 0)
? (*pmf.faddr)(ptr)  // nonvirtual invocation
: (*ptr- >vptr[pmf.index](ptr)  // virtual invocation

对付如下的函数挪用:

(pA.*pmf)(pB);  // pA、pB均是Point3d工具

会被转化成:

pmf.iindex < 0
? (*pmf.faddr)(&pA + pmf.delta, pB)
: (*pA._vptr_Point3d[pmf.index].faddr)(&pA + pA._vptr_Point3d[pmf.index] + delta, pB);

***Inline Functions***

#p#分页标题#e#

在inline扩展期间,每一个形式参数城市被对应 的实际参数代替。可是需要留意的是,这种代替并不是简朴的一一代替(因为这将导致对付实际参数的 多次求值操纵),而凡是都需要引入姑且性工具。换句话说,假如实际参数是一个常量表达式,我们可 以在替换之前先完成其求值操纵;后继的inline替换,就可以把常量直接绑上去。

举个例子,假 设我们有以下简朴的inline函数:

inline int min(int i, int j)
{
return i < j ? i : j;
}

对付以下三个inline函数挪用:

minval = min(val1,val2);
minval = min(1024,2048);
minval = min(foo(),bar() +1);

会别离被扩展为:

minval = val1 < val2 ? val1 : val2;  // 参数直接代换
minval = 1024;  // 代换之后,直接利用常量
int t1;
int 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函数中的局部变量,再加上有副浸染的参数 ,大概会导致大量姑且性工具的发生。出格是假如它以单一表达式被扩展多次的话。新的Derived工具的 地点必需调解,以指向其Base2 subobject.

    关键字:

在线提交作业