Home > OS >  typedefing a pointer recognized by C to an inner C class
typedefing a pointer recognized by C to an inner C class

Time:12-07

I have a class that I want to share between C and C , where C is only able to get it as a pointer. However because it is an inner class it cannot be forward-declared. Instead this is what our current code does in a common header file:

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

typedef Container::Object * PObject;
#else

typedef void* PObject;

#endif

This looks like it violates the one definition rule (ODR), because C and C see different definition using the #ifdef. But because this is a pointer, I'm not sure if this creates a real problem or not. C only uses the pointer in order to pass it to C functions, it doesn't directly do anything with it. For instance this code in the common header file:

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

#ifdef __cplusplus
}
#endif /* __cplusplus */

This as how we implemented it in the C file:

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

My questions are:

  1. Would violating the ODR like this cause any real problems?
  2. If so, is there another convenient way to share the class to C?

CodePudding user response:

First of all, type-aliasing pointers is usually a recipe for trouble.
Don't do it.

Second, the "inner" class is an overused concept, so my first reflex would be to consider whether it's really necessary.

If it is necessary, you can define an opaque empty type and derive from it for some type safety:

In a shared header:

struct OpaqueObject;

#ifdef __cplusplus
extern "C" {
#endif

int object_GetX(OpaqueObject* pObject);

#ifdef __cplusplus
}
#endif /* __cplusplus */

In a C header:


struct OpaqueObject {};

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


Implementation:

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

CodePudding user response:

Would violating the ODR like this cause any real problems?

"Real problems"? Maybe. Maybe not.

There is no "mix C/C ODR rule". Rules are "contained" within one programming language, specification are written for one programming language. C has its own rules and C ihas ODR rule. C has its own rules, and C does not know anything about ODR. Other programming languages have their own rules.

You are not violating ODR in C - C sees a single one definition of Container everywhere.

The stuff in extern "C" uses C calling convention, that I could assume that it also adheres to the C language. In this case, you are just violating rule https://port70.net/~nsz/c/c11/n1570.html#6.5.2.2p9 . Let's say this is like ODR in C for functions.

If not, you are violating something between languages, that would be ABI. You pass the value of a void * pointer from C side and your function reads a value of Container::Object * pointer from C side. That could be allowed by the specific ABI your compilers are using, it could be unspecified. The safe approach would be to assume that it is not allowed.

is there another convenient way to share the class to C?

C language also offers static type checking just like C , but by using void * you are basically turning it off. Do not mix C with C. Just have a different unique name for C side, you will be way less confused.

Also, pObject vs PObject? Your shift must be fast!

// 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;
}
  • Related