模板和接口的多重继承问题

时间:2015-02-06 00:10:13

标签: c++ templates inheritance multiple-inheritance

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

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

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

我创建了一个简单的例子来说明我想要实现的目标。但我的代码显然更复杂。

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,也没有显示出来。 另外,如果我在派生中切换继承顺序,我会从BaseAction获得奇怪的行为。 看起来我在vtable上发生了一些奇怪的事情。这可能与模板参数有关吗? 如果我使用(Derived<short> *)进行投射,它可以正常工作。 我想了解我做错了什么,以及如何正确地做MI和模板。

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

3 个答案:

答案 0 :(得分:2)

底线是,如果你知道什么是*将转换为,那么在转换为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()实际上是对base_interface_instance->vtable[2]()的来电。

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

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

DerivedInterface布局如下所示:

DerivedInterface layout

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

Derived class layout

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

因此,要重复底线,如果您知道转换为,那么在转换为void *时,将转换为相同的指针类型。在多继承场景中,任何其他东西都有可能失败。

答案 1 :(得分:1)

您发布的代码示例似乎不可编译:

DerivedInterface *c = (DerivedInterface *)clientData;
c->DerivedAction(.1);

但您发布的DerivedInterface课程的定义是:

class DerivedInterface
{
public:
    DerivedInterface(){};
    virtual ~DerivedInterface(){};
    virtual DerivedAction()=0;
};

您的DerivedAction类定义的唯一DerivedInterface()方法是不带参数的方法,而示例代码则传递floatdouble参数。< / p>

甚至无法编译。如果您想帮助弄清楚为什么没有从代码中获得预期的结果:

  1. 发布最低限度的完整示例。一个最小的代码片段,它将构建和编译,但会演示您的问题。

  2. 说明您期望的结果,以及您使用示例代码获得的结果。

  3. 发布代码的随机片段,这些代码片段甚至无法编译,但是提出假定代码编译和运行的问题,就像在这里一样,不可能产生有用的答案。

答案 2 :(得分:1)

您正在向Derived*投射void*,然后尝试将void*强制转换为DerivedInterface*,您无法使用多重继承执行此类强制转换(或者可靠地执行任何转换)继承),DerivedInterfaceDerived不在同一地址。使用void*唯一可以做的就是将其强制转换回原始对象。

以下任何一种都可以使用:

Derived<short>* a = new Derived<short>;
DerivedInterface *c = a;
c->DerivedAction(.01);

Derived<short>* a = new Derived<short>;
BaseInterFace *cd = a;
DerivedInterface *c = dynamic_cast<DerivedInterface*>(cd);
c->DerivedAction(.01);

我必须使用void*,您必须首先将其强制转换回Derived<short>。请考虑避免使用c-style强制转换,如果使用c ++,编译器可以获取某些类型的无效强制转换。