Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding namespaces to C++ implementations that have a C header

Tags:

c++

c

namespaces

We have a large project with C and C++ code.

For every C++ implementation, apart from the C++ header, we usually have provide a C-header to allow functionality to be available for .c files, also.

So, most of our files look like so:

foo.hpp:

class C { 
    int foo();
};

foo.h:

#ifdef __cplusplus
extern "C" {
    typedef struct C C;  // forward declarations
#else
    class C;
#endif

    int foo( C* );               // simply exposes a member function
    C*  utility_function( C* );  // some functionality *not* in foo.hpp  

#ifdef __cplusplus
}
#endif

foo.cpp:

int C::foo()  { /* implementation here...*/ }

extern "C"
int foo( C *p ) { return p->foo(); }

extern "C"
C*  utility_function ( C* ) { /* implementation here...*/ }

QUESTION:

Suppose I wanted to add a namespace to the class like so:

foo.hpp:

namespace NS {
    class C { 
        int foo();
    };
}

what is the best scheme to follow in the C-headers?

I have considered a few options, but I'm looking for the most elegant, safe and easy to read. Is there a standard way you use?


Here are the options I've considered: (I've ommitted the extern "C" constructs for simplicity)

  • Option 1: fool the compiler by adding some code in each header:

foo.h

#ifdef __cplusplus
    namespace NS { class C; }  // forward declaration for C++ 
    typedef NS::C NS_C;
#else
    struct NS_C;  // forward declaration for C
#endif

int foo( NS_C* );
NS_C*  utility_function( NS_C* );

this adds some complexity to the header, but keeps the implementations unchanged.


  • Option 2: Wrap the namespace with a C-struct:

    Keeps the header simple but makes the implementation more complex:

foo.h

struct NS_C;  // forward declaration of wrapper (both for C++ and C)

int foo( NS_C* );
NS_C*  utility_function( NS_C* );

foo.cpp

namespace NS {
    int C::foo() { /* same code here */ }
}

struct NS_C {     /* the wrapper */
    NS::C *ptr;
};

extern "C" 
int foo( NS_C *p ) { return p->ptr->foo(); }

extern "C"
NS_C *utility_function( NS_C *src ) 
{
    NS_C *out = malloc( sizeof( NS_C ) );  // one extra malloc for the wrapper here...
    out->ptr = new NS::C( src->ptr );
    ...
}

are these the only schemes? Are there any hidden disadvantages in any of these?

like image 508
Grim Fandango Avatar asked Nov 14 '11 00:11

Grim Fandango


1 Answers

I find it easier to factor code in a way so that foo.h only contains the bare minimum of C++ specifics while foo.hpp takes care of the gritty bits.

The file foo.h contains the C API and should not be included directly from C++ code:

#ifndef NS_FOO_H_
#define NS_FOO_H_

// an incomplete structure type substitutes for NS::C in C contexts
#ifndef __cplusplus
typedef struct NS_C NS_C;
#endif

NS_C *NS_C_new(void);
void NS_C_hello(NS_C *c);

#endif

The file foo.hpp contains the actual C++ API and takes care of including foo.h into C++ files:

#ifndef NS_FOO_HPP_
#define NS_FOO_HPP_

namespace NS {
    class C {
    public:
        C();
        void hello();
    };
}

// use the real declaration instead of the substitute
typedef NS::C NS_C;
extern "C" {
#include "foo.h"
}

#endif

The implementation file foo.cpp is written in C++ and thus includes foo.hpp, which also pulls in foo.h:

#include "foo.hpp"
#include <cstdio>

using namespace NS;

C::C() {}

void C::hello() {
    std::puts("hello world");
}

C *NS_C_new() {
    return new C();
}

void NS_C_hello(C *c) {
    c->hello();
}

If you do not want to make the C API available to C++ code, you could move the relevant parts from foo.hpp to foo.cpp.

As an example for use of the C API a basic file main.c:

#include "foo.h"

int main(void)
{
    NS_C *c = NS_C_new();
    NS_C_hello(c);
    return 0;
}

This example has been tested with the MinGW edition of gcc 4.6.1 using the following compiler flags:

g++ -std=c++98 -pedantic -Wall -Wextra -c foo.cpp
gcc -std=c99 -pedantic -Wall -Wextra -c main.c
g++ -o hello foo.o main.o

The code assumes that the types NS::C * and struct NS_C * have compatible representation and alignment requirements, which should be the case virtually everywhere, but as far as I know is not guaranteed by the C++ standard (feel free to correct me if I'm wrong here).

From a C language perspective, the code actually invokes undefined behaviour as you're technically calling a function through an expression of incompatible type, but that's the price for interoperability without wrapper structures and pointer casts:

As C doesn't know how to deal with C++ class pointers, the portable solution would be to use void *, which you should probably wrap in a structure to get back some level of type safety:

typedef struct { void *ref; } NS_C_Handle;

This would add unnecessary boilerplate on platforms with uniform pointer representation:

NS_C_Handle NS_C_new() {
    NS_C_Handle handle = { new C() };
    return handle;
}

void NS_C_hello(NS_C_Handle handle) {
    C *c = static_cast<C *>(handle.ref);
    c->hello();
}

On the other hand, it would get rid of the #ifndef __cplusplus in foo.h, so it's actually not that bad, and if you care about protability, I'd say go for it.

like image 71
Christoph Avatar answered Sep 21 '22 03:09

Christoph