我正在开发一个使用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做错了什么?
答案 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对象的布局:
如您所知,由于纯虚方法,您实际上无法实例化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布局如下所示:
多重继承有点不同。 Derived类的实例如下所示:
现在应该清楚为什么要调用错误的函数。在从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
()方法是不带参数的方法,而示例代码则传递float
或double
参数。< / p>
甚至无法编译。如果您想帮助弄清楚为什么没有从代码中获得预期的结果:
发布最低限度的完整示例。一个最小的代码片段,它将构建和编译,但会演示您的问题。
说明您期望的结果,以及您使用示例代码获得的结果。
发布代码的随机片段,这些代码片段甚至无法编译,但是提出假定代码编译和运行的问题,就像在这里一样,不可能产生有用的答案。
答案 2 :(得分:1)
您正在向Derived*
投射void*
,然后尝试将void*
强制转换为DerivedInterface*
,您无法使用多重继承执行此类强制转换(或者可靠地执行任何转换)继承),DerivedInterface
与Derived
不在同一地址。使用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 ++,编译器可以获取某些类型的无效强制转换。