【问题标题】:Using C++ struct with virtual members (i.e. non-POD) in C在 C 中使用带有虚拟成员(即非 POD)的 C++ 结构
【发布时间】:2018-04-29 00:19:47
【问题描述】:

this 等问题中,只要所有成员的类型相同、顺序相同,并且没有声明虚拟成员,就尽可能解释 C++ 类/结构和 C 结构之间的兼容性。

这是我的问题。我有虚方法,我非常希望在用 C++ 操作结构时保留它们。

让我们来看看这个玩具示例。它是在单个头文件中定义的 C 和 C++ 兼容结构。

mystr.h:

#ifdef __cplusplus
#include <string>
struct mystr_base {
    virtual ~mystr_base() {}

    virtual std::string toString() = 0;
};
#endif

#ifdef __cplusplus
extern "C" {
#endif

struct mystr
#ifdef __cplusplus
: public mystr_base
#endif
{
    const char* data;

#ifdef __cplusplus
    std::string toString() {
        return std::string(data);
    }
#endif
};

#ifdef __cplusplus
}
#endif

这可能不是很漂亮,但可以作为示例。在实际场景中,C 和 C++ 变体可能位于不同的标头中,而 C++ 结构扩展了 POD 结构。无论实施如何,对齐问题仍然存在。

在此示例中,如果编写的 C 程序将 mystr 的实例传递给 C++ 函数,则 vtable 将干扰对齐:

test.h:

#include "mystr.h"

#ifdef __cplusplus
extern "C"
#endif
void mycxxfunc(struct mystr str);

test.cpp:

#include <stdio.h>
#include "test.h"

void mycxxfunc(mystr str) {
    printf("mystr: %s\n", str.data);
}

main.c:

#include "test.h"

int main(int argc, char** argv) {
    const char* testString = "abc123";
    struct mystr str;
    str.data = testString;
    mycxxfunc(str);
}

$ g++ -c test.cpp && gcc main.c test.o
$ ./a.out
Segmentation fault (core dumped)

(假设这是因为 C++ 函数试图从结构分配内存的末尾读取data

实现这种 C-C++ 互操作性同时仍保留在 C++ 中使用虚函数的能力的最佳方法是什么?

【问题讨论】:

  • 结构在c中没有任何成员函数。
  • @user0042 在 C 中编译时,没有定义成员函数。在 C++ 中拥有一个包含成员函数的 POD 结构是完全有效的,它在 C 中使用时将具有完全相同的内存布局。
  • 我建议尝试以这种方式混合 C 和 C++。将它们分开。有一个 C++ 类,它有一个 POD 结构的成员变量。如果需要,将令牌(void*)传递给 C++ 类实例的 API 的 C 端,并在该 C/C++ API 屏障处将其打包(到 void*,或返回到 C++ 类指针) .
  • 一般来说,多态类不能与C“等价”兼容,因为C没有等价物。您需要知道这些类是如何编译的。那么您的代码将仅适用于编译器的 ABI。

标签: c++ c virtual-functions memory-layout vptr


【解决方案1】:

我不建议您将头文件与#ifdefs 混淆。

在这种情况下,如果您想同时保留某种虚拟化和 C 兼容性,您应该做的第一件事是:

  1. 使您的 C++ 和 C 类型成为指向表示的不透明指针。
  2. 将实现细节放在 .cpp 文件中。

一个想法随之而来。

头文件:

struct MyStrImpl;

struct MyStr {
   MyStrImpl * impl;
};

extern "C" MyReturnType myFunction(MyStr myStr);

.cpp 文件中的实现:

struct MyCppString {
    virtual ...
};

#ifdef __cplusplus
struct MyStrImpl : public MyCppString {

};
#else
struct MyStrImpl {

};
#endif
MyStr::MyStr() : impl{new MyStrImpl{}} {

}

这样你就有了一个可以在 C 和 C++ 中使用的类型。

优点:

  • 头文件中没有#ifdef
  • 与 C/C++ 兼容并且可以从两种语言中使用。

缺点:

  • 由于外部“C”而失去过载。
  • 必须使用 C 风格的接口(但可以在 .cpp 文件中使用类似 C++ 的虚函数接口来实现。

你不能同时在一个头文件中同时拥有 C 兼容类型和虚函数而不用 #ifdef 弄乱它,我不建议这样做,因为如果你需要重构代码会很不方便。

【讨论】:

  • 我不喜欢这个答案,因为它使使用变得笨拙。例如,C 中的初始化类似于MyStrImpl* impl = (MyStrImpl*) malloc(sizeof(MyStrImpl)); MyStr str; str.impl = impl;,而在 C++ 中使用方法将采用str.impl-&gt;toString() 的形式。这对于内部代码可能没问题,但对于 API 来说是不可接受的。
  • 如果您需要更花哨的东西,无论如何,如果您想要不同的接口,解决方案相当于复制 C 和 C++ 实现。您可以使用当前解决方案添加 C 函数以进行构造/破坏。
  • 当然,我可以在 C 中创建一个构造函数。这必须假设调用者可以使用 malloc(可能并非总是如此)。它更多的是关于易用性。无论是 C 还是 C++,结构的用户都不必担心实现细节,只需按语义编写代码即可。我想知道用户定义的包装是否会做我想要的。
  • 将构造函数隐藏在 C 接口后面的事实并不意味着将使用 malloc。我了解您对易用性的看法,但这会使实施变得复杂。我在这里的建议是在这种情况下拆分 C 和 C++ 实现,或者您可以选择使用#ifdefs 来混乱事物。你不能保证没有间接AFAIK的布局。
  • 我个人更喜欢艰难的做事方式。如果我可以通过适当的抽象使其易于使用,我会的。无论如何,我可以通过拆分 C 和 C++ 标头来避免大量 #ifdefs
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-11
  • 2012-12-12
  • 2010-12-21
  • 2010-09-07
  • 1970-01-01
  • 2014-12-24
相关资源
最近更新 更多