将具有多个继承的泛型类句柄转换为基类即接口

问题描述

我正在为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_HandleT_Mouse内有两个T_Keyboard基类。由于两个类T_IDeviceInfoT_IDeviceControl都包含一个独立的基类T_Handle,因此从T_MouseT_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();
}

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...