问题描述
我正在为c ++库开发c-api包装器。我还是c ++库的维护者。
到目前为止,我一直在工作的基本策略是为库中的每个c ++类提供一个不透明的句柄。 我将句柄声明为结构:
struct Handle_;
typedef struct Handle_ *Handle;
然后在我的c-api包装标头中,有一个用于类的每个成员函数的函数,包括创建和销毁。我已经按照这篇文章中的描述实现了它: https://stackoverflow.com/a/2045860/3237961
但是知道我有以下问题:
注意:要重现此问题,不需要c-api包装器。这都是关于投射的。
假设我有一个继承自两个抽象类(即接口)的类:
class T_IDeviceInfo {
public:
virtual int getDeviceType() = 0;
};
class T_IDeviceControl {
public:
virtual void open() = 0;
};
class T_Device : public T_IDeviceInfo,public T_IDeviceControl {
public:
int getDeviceType() override {
std::cout << "T_Device::getDeviceType()" << std::endl;
return 0;
}
void open() override {
std::cout << "T_Device::open()" << std::endl;
}
};
在下面的代码中,我使用上面声明的类:
int main(){
//Prints the device type message and the open message from T_Device
T_Device* device = new T_Device();
device->getDeviceType();
device->open();
//works
std::cout << "cast as derived class" << std::endl;
reinterpret_cast<T_IDeviceInfo*>(device)->getDeviceType();
reinterpret_cast<T_IDeviceControl*>(device)->open();
std::cout << std::endl;
void* handle = reinterpret_cast<void*>(device);
//works
std::cout << "cast handle as derived class" << std::endl;
reinterpret_cast<T_Device*>(handle)->getDeviceType();
reinterpret_cast<T_Device*>(handle)->open();
//does not work
std::cout << "cast handle as base class (i.e. interface)" << std::endl;
reinterpret_cast<T_IDeviceInfo*>(handle)->getDeviceType();
reinterpret_cast<T_IDeviceControl*>(handle)->open();
}
输出如下:
T_Device::getDeviceType()
T_Device::open()
cast as derived class
T_Device::getDeviceType()
T_Device::open()
T_Device::getDeviceType()
T_Device::open()
cast handle as derived class
T_Device::getDeviceType()
T_Device::open()
cast handle as base class (i.e. interface)
T_Device::getDeviceType()
T_Device::getDeviceType()
所以问题出在最后一节。当我将通用句柄转换为基类(接口)时,将在该基类中声明其功能。似乎它总是调用继承顺序第一的类的函数。 到目前为止,我所读到的是,强制类型转换现在与类的大小无关,并且错误地计算了偏移量。这个对吗?你能为我澄清一下吗?
我做过一些研究,发现以下几点:
使用static_cast<>()
而不是reinterpret_cast<>()
时,得到的输出是相同的。
我知道使用不同的转换方法会有区别,但是我很困惑哪种方法最适合我的任务。
所有有关c wrapper api的文章都提出了类似于上面链接的答案中的方法,并使用reinterpret_cast或static_cast。
甚至有可能将泛型句柄强制转换为派生类的基类。由于接口在不同的设备类中使用,因此可以节省很多工作,因此我不需要为每个设备重写c包装函数。
感谢您的帮助。 谢谢!
解决方法
我认为,static_cast<>()
和reinterpret_cast<>()
根本无法实现您要求的这种转换,因为您具有多重继承。如果有多个基类,则将一个类转换为其基类会涉及一些置换魔术。
我的建议是使用static_cast<>()
强制转换为通用类T_Device
,然后使用dynamic_cast<>()
完成实际工作:
T_Device *const device = static_cast<T_Device *>(handle);
T_IDeviceInfo *const info = dynamic_cast<T_IDeviceInfo *>(device);
T_IDeviceControl *const control = dynamic_cast<T_IDeviceControl *>(device);
但是据我了解,您有一些设备类,例如
class T_Keyboard : public T_IDeviceInfo,publibc T_IDeviceControl { /* ... */ };
class T_Mouse : public T_IDeviceInfo,publibc T_IDeviceControl { /* ... */ };
例如,有一个工厂功能
void *get_device_handle(const char *name) {
if (strcmp(name,"keyboard") == 0) {
return new T_Keyboard(/* ... */);
} else if (strcmp(name,"mouse") == 0) {
return new T_Mouse(/* ... */);
} else {
return NULL;
}
}
现在的问题是,如何编写类似这样的函数
void open_device(void *handle) {
T_IDeviceControl *const control = /* some casting from handle */
control->open();
}
对吗?一种可能的解决方案是为所有接口定义一个通用的虚拟基类:
class T_Handle {
public:
// By the way: You forgot this important one! In polymorphic classes
// the destructor must be virtual.
virtual ~T_Handle() { }
};
class T_IDeviceInfo : public virtual T_Handle { /* ... */ };
class T_IDeviceControl : public virtual T_Handle { /* ... */ };
基类T_Handle
必须是虚拟的,否则T_Handle
和T_Mouse
内有两个T_Keyboard
基类。由于两个类T_IDeviceInfo
和T_IDeviceControl
都包含一个独立的基类T_Handle
,因此从T_Mouse
到T_Handle
的向上广播将是模棱两可的。
get_device_handle()
函数必须看起来像这样:
void *get_device_handle(const char *name) {
if (strcmp(name,"keyboard") == 0) {
return dynamic_cast<T_Handle *>(new T_Keyboard(/* ... */));
} else if (strcmp(name,"mouse") == 0) {
return dynamic_cast<T_Handle *>(new T_Mouse(/* ... */));
} else {
return NULL;
}
}
open_device()
函数如下所示:
void open_device(void *handle) {
T_Handle *const base = static_cast<T_Handle *>(handle);
T_IDeviceControl *const control = dynamic_cast<T_IDeviceControl *>(base);
control->open();
}