引言: 在一个产品中, 看到把一个实现类指针 this, 同时传递给接口Tx, Rx 有感。
当时这个接口没有写好,子类和父类在函数原型定义上一个是const, 一个未定为const, 使得基类传递不给继承类。
这种差别,已经不能称为父子关系了。
接口工作不了,才不得不多看了一眼。并认真的思考了一下问题。
--------------------------------------------------------------------------------
关于多重继承指针回退传递的细节。
1. 问题提出
c 语言时代,有回调函数。 是说传递来一个函数指针,我们来调用这个函数。
这样我们并不关心这个函数的实现,而只是关心这个函数的接口。即它的参数类型,参数个数。
c++时代,进一步表现为。传递过来一个对象指针。我们来调用这个对象的成员函数。
这就是类的回调函数。对于正在写底层逻辑的你来说, 你不关心这个回调对象的实现,而只是
关心它的接口。
c时代,传来的函数可以有不同的地址,从而有不同的表现。
c++时代,这个传来的指针可以是不同的对象。因而也可以有不同的表现形式。但实现这种变化必须要求
各对象具有相同的基类。基类中要求变化的函数声明为虚函数,并且当函数需要不同的表现时,继承类重载基类对应的函数。
看起来,c++一下子比c复杂了不少。
c++实现这种要求的方法是通过建立虚函数表来完成的。
在还不知道未来继承类是怎样实现的情况下, 基础的逻辑控制函数就已经按照基类的要求来完成它的处理流程了。
这里碰到了一种情况。 一个实现类继承了两个虚基类Rx, Tx.
在书写函数时,已经写好了控制流程。 并且这个实现类 implemnet RXTX 也已经完成。
问题是,这个实现类对象,怎样传递给虚基类指针Rx, Tx.
我们知道,子类会退化为父类指针,对应单继承很好理解,对应多重继承, 难道说这个子类即能退化为Tx, 又能退化为Rx吗?
带着这种疑惑, 我研究了这个问题的汇编代码。 得出了结论:
子类在退化为父类指针时,并不是简单的指针赋值,而有可能调整指针位置,这就是c++编译器的强大复杂之处。
2. 研究这个问题的例子。(源码)
#include <stdio.h>
class Tx
{
public:
virtual void tx_exec() {
printf("Tx not implement!\n");
}
private:
char a;
char b;
};
class Rx
{
public:
virtual void rx_exec() {
printf("Rx not implement!\n");
}
private:
int i;
int j;
};
class RxTx: public Tx, public Rx
{
public:
void tx_exec() {printf("Tx has implement!\n");}
void rx_exec() {printf("Rx has implement!\n");}
};
void test(Tx *pTx, Rx *pRx)
{
printf("pTx:%p\n",pTx);
printf("pRx:%p\n",pRx);
pTx->tx_exec();
pRx->rx_exec();
}
int main()
{
RxTx obj;
printf("obj:%p\n",&obj);
test(&obj, &obj); // 奥妙就在这里, 前一个&obj, 与后一个&obj 是不同的地址被压栈。
// 因为obj 包含两个类,前一个地址&obj 指向 Tx 对象部分, 后一个地址&obj 指向 Rx对象部分。
// Tx对象部分,Rx对象部分第一个参数分别对应各自的虚函数表。从而实现完美退化。
return 0;
}
附上一个结果:
obj:0x7fff78dc7b60
pTx:0x7fff78dc7b60
pRx:0x7fff78dc7b70
Tx has implement!
Rx has implement!
阅读此文,希望你对程序架构一词有更深的理解。 架构就是架子。就是在还没有实现类的时候,就已经用基础类在运转了。
体现"实现"与"接口"的分离。 实际上就是基类和继承类分离。 就是c++ 继承概念。不同的对象表现,就是多态概念。阿弥陀佛!...
补充: 如果一个实现类需要很多的接口时, 可以采用SendMessage 的结构。 把消息用命令类型来分类。 这样就可以省略很多接口指针。
MFC 的sendmessage, postmaessage 就是很好的例子。