c++ - 模板和接口(interface)的多重继承问题

标签 c++ templates inheritance multiple-inheritance

我正在开发一个使用 tcl 解释器的项目,我们将 c++ 对象和方法绑定(bind)到 tcl 命令。绑定(bind)是具有 ClientData 指针参数(几乎是 void*)的 C++ 函数。然后我们将其转换为我们期望的对象,并与该对象交互。

我正在努力合并一组模板化的类。当我将这些类绑定(bind)到 tcl 命令时,我不知道模板参数,因此我使用抽象“接口(interface)”基类与它们交互并将模板参数放入实现类中,因此我不需要模板参数使用它们。

最重要的是,我有一个处理所有常见操作的基类,以及执行其他所有操作的派生类,两者都有接口(interface)类。

我创建了一个简单的示例来说明我想要完成的任务。但我的代码显然更复杂。

StackTest.h

#include <string>
#include <iostream>
#define STRLEN 1000
class BaseInterFace
{
public:
    BaseInterFace(){};
    virtual ~BaseInterFace(){};
    virtual const char *GetNameOfClass() const=0;
    virtual void BaseAction()=0;
};

template<typename T>
class Base: public BaseInterFace
{
public:
    Base(){};
    virtual ~Base(){};
    virtual const char *GetNameOfClass() const
    {
        std::cout << "This is " << this << std::endl;
        return "Base";
    }

    virtual void BaseAction(){
        printf("Base Action Done.\n");
    }
};
class DerivedInterface
{
public:
    DerivedInterface(){};
    virtual ~DerivedInterface(){};
    virtual void DerivedAction(double value)=0;
};

template<typename T>
class Derived :
public Base<T>,public DerivedInterface
{

public:
    char tclName_[STRLEN];
    Derived(){};
    virtual ~Derived(){};
    virtual const char *GetNameOfClass() const
    {   std::cout << "This is " << this << std::endl;
        return "Derived";
    }
    virtual void DerivedAction(double value){
        printf("Derived Action Done.\n");
    }

};

简单示例 这个例子没有意义,因为我知道类型。但它会在我不这样做时进行模拟,这就是我使用它的方式。

Derived <short> *a = new Derived <short>();
void *cd = (void *)a;
printf("Base_BaseActionMtd\n");
BaseInterFace *b = (BaseInterFace *)cd;
printf("InputClass: %s\n",b->GetNameOfClass());
b->BaseAction();
printf("Done!\n");
BaseInterFace *b2 = (BaseInterFace *)cd;
printf("InputClass: %s\n",b2->GetNameOfClass());
DerivedInterface *c = (DerivedInterface *)cd;
c->DerivedAction(.01);
printf("Done!\n");

输出:

Base_BaseActionMtd
This is 0x1089fdc00
InputClass: Derived
Base Action Done.
Done!
This is 0x1089fdc00
InputClass: Derived
This is 0x1089fdc00
Done!

派生操作未发生,且其调用 GetNameOfClass?我不明白。即使我将 printf 放入派生接口(interface)中,它也不会显示。 另外,如果我切换导出中的继承顺序,我会从 BaseAction 中得到奇怪的行为。 看起来 vtable 发生了一些奇怪的事情。这可能与模板参数有关吗? 如果我使用 (Derived<short> *) 进行转换效果很好。 我想了解我做错了什么,以及如何使用模板正确地进行 MI。

据我所知,操作没有任何歧义。 所以我的问题有两个。
1) MI 是正确的答案吗?有很多很多类需要派生,另一个继承链将使用相同的 Base,所以我想重用尽可能多的代码。但如果有更好的方法,我宁愿避免 MI。 2)我的 MI 做错了什么?

最佳答案

底线是,如果您知道 void* 将转换为,那么在转换为 void* 时转换相同的指针类型。在多重继承场景中,任何其他操作都有可能失败。根据您的情况,替换此:

Derived<short> *d = new Derived<short>();
void *cd = (void*)d;
DerivedInterface *di = (DerivedInterface*)cd;
di->DerivedAction(.01);

这样:

Derived<short> *d = new Derived<short>();
DerivedInterface *di = (DerivedInterface*)d;
void *cd = (void*)di;
DerivedInterface *di2 = (DerivedInterface*)cd;
di2->DerivedAction(.01);

第一个代码错误而第二个代码正确的原因是第二个代码在与 void* 之间的转换中使用相同的指针类型。

让我们看一下对象布局,以了解为什么会出现这种情况,以及为什么您的示例最终会调用错误的函数。 (注意:这个讨论在概念上是正确的,但实现与此不完全匹配。它足够接近,足以向您展示如何正确编码。)

首先,让我们看一下 BaseInterface 对象的布局:

layout of BaseInterface object

如您所知,由于纯虚方法,您实际上无法实例化 BaseInterface 对象,但在使用 BaseInterface 指针时,这是编译器期望的布局。有一个编译器生成的成员变量 vtable,它指向一组函数指针,每个虚拟函数一个。对 base_interface_instance->GetNameOfClass() 的调用实际上是对 base_interface_instance->vtable[1]()base_interface_instance->BaseAction() 的调用code> 实际上是对 base_interface_instance->vtable[2]() 的调用。

Base 对象的布局看起来就像 BaseInterface 的布局。当您创建这种类型的对象时,vtable 保存指向基类的每个虚函数的实现的指针。对 base_instance->GetNameOfClass() 的调用看起来就像对 BaseInterface 对象 base_instance->vtable[1] 所做的那样。

如果 Base 有任何超出 BaseInterface 中定义的附加虚拟函数,它们就会被放置在 vtable 的末尾。这就是为什么,通过单一继承,您可以在继承链上进行简单的向上和向下转换,并安全地使用生成的指针(只要该对象确实是您要转换到的类型或从中派生的类型)。它们都使用相同的vtable指针来调用虚函数。更多的派生类恰好知道 vtable 进一步向下扩展,从而可以访问稍后在继承链中定义的虚拟函数。

DerivedInterface 布局如下所示:

DerivedInterface layout

多重继承有点不同。派生类的实例如下所示:

Derived class layout

现在应该清楚为什么调用了错误的函数。从 void* 转换为 DerivedInterface* 后,编译器认为它正在使用 DerivedInterface 对象的内存布局,但它实际上指向反射(reflect) BaseInterface 布局的对象部分。 c->DerivedAction() 正在调用 c->vtable[1],但 vtable 指向 BaseInterface 的虚拟方法(Derived 类布局中的 vtable1),而不是 DerivedInterface 的方法(派生类布局中的 vtable2)。 vtable[1]Base::GetNameOfClass(),而不是 Derived::DerivedAction()

所以,重复一下底线,如果您知道 void* 将被转换为什么,那么在转换为 void* 时,从相同的指针类型进行转换。在多重继承场景中,任何其他操作都有可能失败。

关于c++ - 模板和接口(interface)的多重继承问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28356435/

相关文章:

c++ - 错误 C2678 : binary '==' : no operator found which takes a left-hand operand of type (or there is no acceptable conversion)

c++ - (2012) Visual C++ LNK2019 错误,也许是模板问题?

python - 为什么 Python 中的基类需要扩展对象?

c++ - 嵌套类的部分特化

c++ - 如何有效地从一定长度的字符串 vector 中删除单词

c++ - CUDA推力: checking for NULL pointers

c++ - 程序生成无缝分形噪声纹理

c++ - 如果参数类型可转换,则将函数类型转换为不同

c++ - 将 'typedef' 从基础类传播到 'template' 的派生类

c++ - 如何继承模板函数?