转载

C++成员函数语意学

一开始,C++只支持非静态成员函数,后来加入了虚拟成员函数,最后才加入了静态成员函数。通过研究Point类,深入成员函数语意学学习。

class Point
{
public:
Point(float p): _x(p){ _y += 1; }
virtual ~Point() {};

Point func();
Point func() const;
virtual Point vfunc();
static Point sfunc();

float x() const { return _x; }
virtual float y() const { return 0; }
virtual float z() const { return 0; }

virtual Point& mult(float) = 0;
private:
float _x;
static float _y;
};
Point Point::func()
{
return Point(2*_x);
}
Point Point::func() const
{
return Point(2*_x);
}
Point Point::vfunc()
{
return Point(2*_x);
}
Point Point::sfunc()
{
return Point(2*_y)
}

非静态成员函数

非静态成员函数的设计准则为:“非静态成员函数至少和一般的非成员函数有相同的效率”。

编译器通过将成员函数实体转换成为对等的非成员函数实体。编译器是如何做到这一点?以Point类 func成员函数 为例。

1、改写函数签名(原型),加入额外参数 this指针

// non-const non-static member function
Point Point::func(){};
Point Point::func(Point* const this);// convertion by compiler

//const non-static member function
Point Point::func()const{};
Point Point::func(const Point* const this); // convertion by compiler

2、改写成员函数的存取操作

Point Point::func(Point* const this)
{
return Point(2*this->_x);
}

3、将成员函数改写成外部函数

extern func__5PointFv(register Point* const this); // name mangling

4、优化后伪代码

void func__5PointFv(register Point* const this, Point &__result)
{
__result.Point::Point(this->_x + 1);
return;
}

5、调用操作

Point p1, p2;
p2 = p1.func(); // equal to func__5PointFv(&p, p2)
func__5PointFv(&p, p2);

虚拟成员函数

虚拟成员函数引入了虚指针vptr和虚函数表,也引入了多态。

虚函数的调用

1、通过类指针对虚函数的调用,会被转换成通过虚指针vptr调用虚函数表中对应的虚函数。Point类 vfunc虚函数 的调用如下:

Point p1;
Point* p2 = &p1;
p2->vfunc(); // equal to (*p2->vptr[1])(p2)
(*p2->vptr[1])(p2);

2、通过类对象对虚函数的调用,总是被编译器向对待一般非静态成员函数一样对待,如下:

Point p;
p.vfunc(); // equal to vfunc__5PointFv(&p)
vfunc__5PointFv(&p);

单一继承下的虚函数与多态

C++类通过虚指针vptr和虚函数表机制实现多态。假设Point2d派生自Point,Point3d派生自Point2d,下图是带有虚函数表的单一继承情况下,类Point、Point2d、Point3d的内存布局:

C++成员函数语意学

现在已经知道加入虚函数后,类对象的内存布局了,那么如何实现多态呢?

Point2d obj;
Point *ptr = &obj;
ptr->y();

在编译器,我们并不知道ptr所指对象的真正类型,但是根据ptr一定能够取到该对象的虚函数表,并且y()函数的地址都被存放在虚函数表的slot3中。编译器就可以做以下处理:

(*ptr->vptr[3])(ptr);

到执行期时,确定了ptr所指向的对象,也就能确定slot3所指的到底是哪一个y()函数实体。

多重继承下的虚函数与多态

相对于单一继承下的虚函数机制,多重继承下其复杂度围绕在第二个及后继的基类身上,以及必须在执行期调整this指针这一点。Point类已经无法满足要求了,看下面的继承体系:

class Base1{
public:
Base1();
virtual ~Base1();
virtual void speakClearyly();
virtual void Base1* clone() const;
protected:
float data_Base1;
}
class Base2{
public:
Base2();
virtual ~Base2();
virtual void mumble();
virtual void Base2* clone() const;
protected:
float data_Base2;
}
class Derived : pulic Base1, public Base2{
public:
Derived();
virtual ~Derived();
virtual void Derived* clone() const;
protected:
float data_Derived;
}

在多重继承下,一个派生类内含n-1个额外的虚函数表(n为上一层基类的数目)。对于类Derived,会有两个虚函数表被编译出来:

  • 一个主要实体,与Base1共享
  • 一个次要实体,与Base2有关

所以Derived类在多重继承情况下的内存布局如下:

C++成员函数语意学

1、第二个或后继的基类指针的地址偏移

用第二个或后继的基类指针指向派生类对象,编译器会在编译期进行调整:

Base2 *pbase2 = new Derived;
//调整后代码
Derived *temp = new Derived;
Base2 *pbase2 = temp ? temp + sizeof(Base1) : 0;

用第二个或后继的基类指针 析构 派生类对象,指针会被调整以求指向Derived对象的起始处。这个偏移操作需要在执行期通过trunk技术(用适当的offset来调整this指针,同时跳到虚函数中去)来完成。

delete pbase2;

2、通过一个指向派生类的指针,调用第二个或后继的基类中继承而来的虚函数,该指针必须要被调整到指向第二个基类子对象处。

Derived *pder = new Derived;
//pder必须被调整sizeof(Base1)个bytes
pder->numble();

3、允许一个虚函数的返回值类型有所变化,可以是基类类型,也可以是派生类类型。

当我们通过第二个或后继的基类的指针来调用这样的虚函数时,必须要将该指针调整到派生类对象的起始地址。

Base2 *pb1 = new Derived;

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

虚拟继承下的虚函数与多态

还没研究。

静态成员函数

静态成员函数的主要特性是 没有this指针 。以下的次要特性统统根源于其主要特性:

  • 不能直接存取其class中的非静态成员
  • 不能被生命为const、volatile或virtual
  • 不需要经过类对象就可以被调用,如Point::sfunc()

1、对静态成员函数的调用也会经过编译器的处理,Point类 sfunc静态函数 的调用如下:

Point p1;
p1.sfunc(); // equal to sfunc__5PointSFv()
sfunc__5PointSFv();

2、对静态成员函数的取地址操作,得到的是一个非成员函数指针,而非成员函数指针。

&Point::sfunc(); // Point (*) ();
&Point::func() // Point (Point::*) ();

成员函数指针

取成员函数地址

取成员函数地址得到的结果是其在内存中真正的地址,然而这个值并不完整,它需要被绑定到某个类对象上,才能够通过它调用该函数。一个指向Point类的成员函数指针声明如下:

double (Point::* pmf)();
double (Point::*coord)() = &Point::x;
// call
(obj.*coord)();
(ptr->*coord)();

虚函数指针

多态机制在虚函数指针情况下仍然适用。

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

对一个虚函数取址,所能获得的只是一个索引值,该索引值对应其在虚函数表中的索引值。通过pmf来调用z(),会被内部转换为一个编译时期的式子。

&Point::~Point; // output: 1
&Point::z(); // output: 2
(*ptr->vptr[(int)pmf])(ptr);

多重继承下的成员函数指针

多态机制在多重继承下的虚函数指针情况下仍然适用。这种情况比较负责,暂不讨论。

原文  http://JerkWisdom.github.io/developing/cplusplus/the-semantics-of-function/
正文到此结束
Loading...