【问题标题】:typedefing a pointer recognized by C to an inner C++ class将 C 识别的指针类型定义为内部 C++ 类
【发布时间】:2022-01-11 17:01:22
【问题描述】:

我想在 C 和 C++ 之间共享一个类,其中 C 只能将它作为指针获取。然而,因为它是一个内部类,它不能被前向声明。相反,这是我们当前代码在一个通用头文件中所做的:

#ifdef __cplusplus
class Container {
public:
    class Object {
        public:
        int x;
    };
};

typedef Container::Object * PObject;
#else

typedef void* PObject;

#endif

这看起来违反了单一定义规则 (ODR),因为 C 和 C++ 使用 #ifdef 看到不同的定义。但是因为这是一个指针,我不确定这是否会造成真正的问题。 C 仅使用指针将其传递给 C++ 函数,它不直接对它做任何事情。比如通用头文件中的这段代码:

#ifdef __cplusplus
extern "C" {
#endif
int object_GetX(PObject pObject);

#ifdef __cplusplus
}
#endif /* __cplusplus */

这就像我们在 C++ 文件中实现它的方式:

int object_GetX(PObject pObject) {
    return pObject->x;
}

我的问题是:

  1. 像这样违反 ODR 会导致任何实际问题吗?
  2. 如果是这样,是否有其他方便的方法可以将课程共享给 C?

【问题讨论】:

  • 我个人不会有typedef Container::Object * PObject; 别名,只有不透明指针别名typedef void* PObject;。然后在需要时使用reinterpret_cast<Container::Object*>(...)
  • 不要将指针隐藏在 typedef 后面。
  • @Someprogrammerdude 我同意这可行,但它需要为每个添加的功能重复,所以我看看我们是否可以跳过这一步。这种方式也可能使 C++ 编译器有能力保护我不传递错误类型的指针,如果它也认为它是 void* 那么我们就失去了它。
  • 我会(仍然是个人)将每个 extern "C" 函数与实际成员 function 匹配,唯一的例外是“create”和“destroy”函数.我也永远不会使用 C++ 程序中的 C API。而且我几乎从不使用简单的“setter”和“getter”函数,而是尝试创建实现行为的函数。最后,我将废弃我的void* 指针,转而使用molbdnilo's answer 中使用的不透明结构。 :)

标签: c++ c inner-classes conditional-compilation one-definition-rule


【解决方案1】:

首先,类型别名指针通常会带来麻烦。
不要这样做。

其次,“内部”类是一个被过度使用的概念,所以我的第一反应是考虑它是否真的有必要。

如果有必要,您可以定义一个不透明的空类型并从它派生以实现某种类型安全:

在共享标头中:

struct OpaqueObject;

#ifdef __cplusplus
extern "C" {
#endif

int object_GetX(OpaqueObject* pObject);

#ifdef __cplusplus
}
#endif /* __cplusplus */

在 C++ 标头中:


struct OpaqueObject {};

class Container {
public:
    class Object : public OpaqueObject {
        public:
        int x;
    };
};


实施:

int object_GetX(OpaqueObject* pObject) {
    return static_cast<Container::Object*>(pObject)->x;
}

【讨论】:

    【解决方案2】:

    像这样违反 ODR 会导致任何实际问题吗?

    “真正的问题”?可能是。也许不会。

    没有“混合 C/C++ ODR 规则”。规则被“包含”在一种编程语言中,规范是为一种编程语言编写的。 C++ 有自己的规则,C++ 有 ODR 规则。 C有自己的规则,C对ODR一无所知。其他编程语言有自己的规则。

    您没有违反 C++ 中的 ODR - C++ 在任何地方都可以看到一个单一的 Container 定义。

    extern "C" 中的内容使用 C 调用约定,我可以假设它也遵守 C 语言。在这种情况下,您只是违反了规则 https://port70.net/~nsz/c/c11/n1570.html#6.5.2.2p9 。假设这就像 C 中函数的 ODR。

    如果不是,那么您违反了语言之间的某些内容,那就是 ABI。您从 C 端传递 void * 指针的值,而您的函数从 C++ 端读取 Container::Object * 指针的值。这可能是您的编译器正在使用的特定 ABI 允许的,它可能未指定。安全的方法是假设它是不允许的。

    还有其他方便的方法可以将课程分享给 C 吗?

    C 语言也像 C++ 一样提供静态类型检查,但使用 void * 基本上是关闭它。不要将 C++ 与 C 混用。只需为 C 端取一个不同的唯一名称,您就不会那么困惑了。

    另外,pObjectPObject?你的班次一定要快!

    // object.hpp
    #ifdef __cplusplus
    class Container {
    public:
        class Object {
            public:
            int x;
        };
    };
    
    typedef Container::Object PObject;
    
    extern "C" {
    #endif  
    
    struct C_PObject {
       void *pnt;
    };
    
    int object_GetX(struct C_PObject obj);
    // ...
    
    // object_c.cpp
    PObject *object_from_c(struct C_PObject obj) {
        return reinterpret_cast<PObject *>(obj.pnt);
    }
    
    extern "C"
    int object_GetX(struct C_PObject obj) {
        return object_from_c(obj)->x;
    }
    

    【讨论】:

    • 感谢您的回答!您能否解释一下根据port70.net/~nsz/c/c11/n1570.html#6.5.2.2p9 所做的违规行为?我不明白这个问题。另外,我正在使用 GCC/G++,你知道去哪里看看这样的东西是否合法吗?我问这一切是因为最后,指针只是某些东西的内存地址,所以从概念上讲,这些技巧不应该能够改变它。首先,这两种语言都允许您定义指向他们甚至不知道的事物的指针。
    • explain the violation 函数 object_GetX 在 C 端被声明为 int object_GetX(void *pObject); 并且 C 代码将调用该函数。该函数在 C++ 端定义为int object_GetX( Container::Object *pObject)。这违反了规则——C 端将调用void * 并且函数是用Container::Object * 定义的——它们不匹配。 like that is legal? 很难明确回答语言的互操作性,因为没有规范来管理它。
    • ABI 是特定于你正在使用的,这里是 x86 on linux uclibc.org/docs/psABI-x86_64.pdf 。太好了,是的,您会争辩说任何类型的指针都是相同的,所以没关系 - 太好了。但编程不是“让它工作一次”,而是保证它会总是工作。谁知道这样的代码会对 LTO 做出怎样的反应,或者什么时候链接器足够聪明,可以进行跨翻译单元优化和类型检查。
    • 不管怎样,在非常相似的不同语言中以不同的方式使用相同的类型对我来说是令人困惑的。我会使用不同的名称来减少意大利面条式代码,这样代码更容易推理和维护,这更重要,使用void * 指针只会在 C 端引起麻烦。但无论如何,可以肯定 - 你的代码会工作,因为两个平台上的指针大小相同。
    猜你喜欢
    • 2021-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多