【问题标题】:C wrapper for C++ library - what about inheritance?C++ 库的 C 包装器 - 继承呢?
【发布时间】:2014-03-04 11:50:59
【问题描述】:

所以我已经阅读了Developing C wrapper API for Object-Oriented C++ code 并且我喜欢我在我的库中采用的方法 - 每个对应的 C++ 类的不透明句柄;避免使用void*

但现在,我面临着关于“接口”和基类的思考。例如,我有一个“通道”类的类层次结构——“通道”的基类和派生的具体类,例如串行通信、内存缓冲区、套接字等。

所以我有:

typedef struct serial_channel serial_channel;
typedef struct socket_channel socket_channel;
typedef struct memory_channel memory_channel;

serial_channel* create_serial_channel();
socket_channel* create_socket_channel();
memory_channel* create_memory_channel();

但我希望能够将其中任何一个传递给函数以将其与“设备”对象相关联:

void associate_device_with_channel(device*, channel*);

在 C++ 中很容易,因为它理解基类。如何在 C 包装库中处理这个问题 - C 中的 channel 是什么类型?

我唯一能想到的是,我必须求助于 void* 来表示一个基类?

typedef void* channel;
void associate_device_with_channel(device*, channel*);

它有效,但可以让我传递 any 指针吗?

另一方面,我可以编写一组与派生通道类匹配的函数:

void associate_device_with_serial_channel(device*, serial_channel*);
void associate_device_with_socket_channel(device*, socket_channel*);
void associate_device_with_memory_channel(device*, memory_channel*);

很冗长,如果我要添加新的频道类型,我也必须在界面中添加新的功能。

我是否缺少某种中间立场? - 像单个函数,但不是 void*?

【问题讨论】:

  • 不要使用多种渠道类型,而是使用单一类型。当且仅当您需要提供仅适用于特定类型通道的专用功能时,才包含类型代码。在这种情况下,必须在运行时强制执行类型检查。
  • 当你将你的基类(struct)作为继承类(struct)的第一个元素时,你可以在C中实现继承效果,利用指向结构对象的指针指向其初始的事实成员。检查此答案以获取示例stackoverflow.com/a/415536/2549281
  • @Dabo 他并没有尝试在 C 中进行继承。他试图通过不透明的 C 句柄公开他的 C++ 继承结构。

标签: c++ c inheritance wrapper


【解决方案1】:

没有完美的方法。您正在尝试使您的函数采用 some 不透明句柄(具有适当基类的句柄)但不是 any 句柄类型(void* 会接受),并且在 C 语言中没有这样的东西。

如果你愿意,你可以提供一个函数,它接受serial_channel* 并返回channel*,另一个是channel 的子类。这使您远离不安全的 C 转换,并且不需要 numfuncs*numderivedclasses 不同的 channel-taking 函数。

就我个人而言,我只想void* 它。毕竟,他们使用的是 C……很明显,他们并不太关心他们的语言是否能保证他们的安全。

【讨论】:

    【解决方案2】:

    首先,我会像这样设置我的结构:

    typedef void base_class;
    struct base_class_impl
    {
        // base class member variables go here
    }
    struct derived_class
    {
        // base class must come first in the derived struct
        struct base_class_impl base;
        // derived class member variables go here
    }
    

    然后,我会将指向 base_class 的指针作为函数的参数:

    int base_class_get_count(base_class *b);
    

    而且我总是会在函数开始时进行转换:

    int base_class_get_count(base_class *b)
    {
        struct base_class *base = (struct base_class *)b;
        // Operate on the object now
    }
    

    这使得base_class_get_count() 甚至可以在派生类型的对象上工作。缺点是它不允许派生类型覆盖方法 - 您必须更进一步,实现您自己的函数指针表,API 调用(如base_class_get_count)根据表中的条目。

    【讨论】:

    • 谢谢克里斯。调度不是问题,因为幕后都是 C++。最终,您的基类仍然是 void*。我想显示意图并创建一个表示基类的 typedef 很有用,即使它是 void*。
    • 你是对的,它仍然是一个空白*。但是,如果您确实希望基类方法接受派生类型的对象,我相信这是 C 中的唯一方法。否则,您必须拥有一个“上帝”结构,其中包含所有派生类(如果它们包含其他成员变量)并传入该类型 - 这在某种程度上违背了 OO 一开始的目的。然后, void* 方法会让您遇到意外传入错误对象的情况 - 编译器不会帮助您。我认为这是一个可以接受的权衡。
    • 是的,在另一个回答中引用 Sneftel 的话——“毕竟他们使用的是 C……显然他们并不太关心他们的语言是否能保证他们的安全。” :-)
    【解决方案3】:

    如果您只针对 GCC 或 Clang(如果您针对 Visual Studio,我怀疑您不会打扰 C),您的选择之一是创建一个具有非标准 __transparent_union__ 属性的联合以列出函数可以接受的类型。接受具有__transparent_union__ 属性的联合参数的函数将接受该联合或其中包含的任何类型。

    union associable_channel
    {
        channel* a;
        serial_channel* b;
        socket_channel* c;
        memory_channel* d; 
    } __attribute__((__transparent_union__));
    
    void associate_device_with_channel(union associable_channel chan);
    
    serial_channel* serial;
    socket_channel* socket;
    memory_channel* mem;
    associate_device_with_channel(serial);
    associate_device_with_channel(socket);
    associate_device_with_channel(mem);
    

    【讨论】:

    • 这是一个很酷的功能,但在这里我会担心。 __transparent_union__ 在放入 serial_channel 并取出 channel 时不会执行任何指针调整。所以在存在多重继承的情况下,这可能会悄悄地把事情搞砸。
    • 有趣的特性,这在概念上与每个成员都有一个非显式联合构造函数相同吗?
    • 它只适用于函数参数。例如,你不能做union associable_channel foo = serial。 (好吧,我们谈论的是一个非标准特性,很明显供应商可以做到这一点,但 GCC 或 Clang 没有指定这种行为。)
    • 至于多重继承,这个问题是真实存在的,但如果 OP 能够承受 C-cast 指向 void* 的指针,那么透明联合可以成为完全一样安全的替代品(阅读:这将默默地在相同的情况下中断),同时保持一些额外的类型表现力。我个人从不使用多重继承,除了没有字段的接口,我知道我不是一个人这样工作。
    • 不幸的是,我使用的是 Visual Studio,所以这对我不起作用。但知道它非常有用。我不知道多重继承问题 - 我明白你的意思,但我没有使用 MI,所以这对我来说不是问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-10
    • 1970-01-01
    • 1970-01-01
    • 2010-11-25
    • 2015-04-19
    • 2014-04-30
    相关资源
    最近更新 更多