Search code examples
c++cnamespacesderived-classinterface-design

How to design a library with paralell interfaces in C and C++


My current project is a medium-sized library that is meant to have a C and a C++ interface at the same time. It centers around a single data type that I want to be accessible from C and C++ functions, because I want to encourage third parties to extend the library by writing functions in either language.

I know about the basics of C/C++ mixing (compare for example http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html) and have come up with the following solution:

My basic design centers around creating a struct in C with all data exposed (this is what my C programmers expect) and deriving a class from it that hides member access, hopefully leading to safer access to the struct for C++ programmers. The problem comes in with the derivation: I want to use namespaces in C++ and hide the C interface. Of course, the C struct itself cannot be hidden (without resorting to the PIMPL idiom), but that's fine for me.

The following example code compiles and runs without apparent errors with C and C++ "client" programs. However, I'm wondering if this solution is valid or if there are better solutions.

Example code:

#ifdef __cplusplus__
extern "C" {
#endif

struct base
{
    char * data;
}

#ifdef __cplusplus__
} // extern "C"
namespace {
extern "C" {
#endif

/* cleanly initialize struct */
struct base * new_base (struct base *);

/* cleanly destroy struct */
void del_base (struct base *);

#ifdef __cplusplus__
} } // namespace, extern "C"

#include<new>

namespace safe {

class base_plus : private base
{
public:
    base_plus () 
    { 
        if (! new_base(this)) 
            throw std::bad_alloc ();
    }

    ~base_plus ()
    {
        del_base (this);
    }
};

} // namespace safe

#endif

Solution

  • 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.