Actually, another way would be to write the full code in C++ and only write a C slim interface over this, using data hiding technics.
namespace Foo {
class Bar {
public:
int property1() const;
std::string const& property2() const;
};
}
And in a C-compatible header:
#ifdef __cplusplus__
extern "C" {
#endif
typedef void* Bar;
Bar foo_bar_new(int i, char const* s);
void foo_bar_delete(Bar b);
int foo_bar_property1(Bar b);
char const& foo_bar_property2(Bar b);
#ifdef __cplusplus__
}
#endif
With the accompanying implementation:
Bar foo_bar_new(int i, char const* s) {
return new Foo::Bar(i, s);
}
void foo_bar_delete(Bar b) {
delete static_cast<Foo::Bar*>(b);
}
int foo_bar_property1(Bar b) {
return static_cast<Foo::Bar*>(b)->property1();
}
char const* foo_bar_property2(Bar b) {
return static_cast<Foo::Bar*>(b)->property2().c_str();
}
The two main advantages are:
- Full-blown C++ code, with fully encapsulated data and all the goodness of a stronger type-system
- Binary stability across releases made easier in the C interface
Note: this is how Clang and LLVM deal with C compatibility, for example.