1. 8
    Can you use a C++ class in C? c c++ thasso.xyz
    1. 14

      I’ve done a lot of wrapping-C++-in-C in my career. This is a good basic intro, but there are several things that can be done better.

      First, it’s much simpler to use extern “C” — you don’t need to go to the lengths of making a macro for it! You just put a curly-braced block after it, and the C-ness applies to everything in that block. The super-common idiom for this is:

      #ifdef __cplusplus
      extern “C” {
      #endif
          // … any number of declarations here ...
      #ifdef __cplusplus
      }
      #endif
      

      Second, using void* to represent an object pointer loses type-safety. It’s quite easy to do better. Another common idiom:

      #ifndef __cplusplus
      typedef struct Rational Rational;
      #endif
      extern_c Rational *make_rational(int numer, int denom);
      extern_c int get_numer(const Rational *r);
      extern_c int get_denom(const Rational *r);
      extern_c void del_rational(Rational **rp);
      

      This tells the C compiler there’s a struct called Rational, but the compiler never sees (nor needs to see) its definition since you only ever declare pointers to it. It’s an opaque type.

      Note that the accessors take a const pointer. This is a very good practice to follow. It makes const Rational* a read-only reference, so you can pass that type around and be assured that the called function(s) won’t modify (or free!) the object. (Unless they’re evil and cast away the const, but you wouldn’t do that, right?)

      As a bonus, if the C++ class Rational is in the default namespace, these actually refer to the same type, so in your C++ implementations of make_rational etc. you don’t need to do any type-casting. However, that’s not usually the case, since a good C++ API puts its declarations in a unique namespace; so generally your implementations do have to cast between e.g. mymath::Rational* and ::Rational*. But it’s a small price to pay for type-safety.

      1. 2

        One other piece that I’ve done when wrapping a complex C++ class structure with a simple C API for Python to call is to make it into a .so so that you don’t have to link your C code against libstdc++ explicitly. The .so exports C symbols that handle all of the C++ bridge stuff and your client doesn’t even need to know that the guts are written in C++.

        1. 1

          You likely meant extern "C" in the function declarations since, like you said before, you don’t really need a macro for it.

          1. 1

            Right, I was copy & pasting and forgot to remove the macro.

        2. 2

          I do think a C++ interface importer would be nice, though obviously polymorphism (both up and down casts) are misery.

          In principle (and functionally this is what happens anyway) it would be nice to have a tool that given:

          // Foo.hpp
          class Foo {
            virtual ~Foo() = 0;
          };
          class Bar : Foo {
            virtual int wibble();
          };
          

          produced

          // Foo.h
           struct Foo;
           struct Bar;
          
           void foo_destroy(struct Foo* _this);
           void bar_wibble(struct Bar* _this);
           void bar_destroy(struct Bar* _this);
          

          With the obvious implementation.

          Honestly it would be nice if C++ considered this use case and it was more readily exposed to C code in a way that didn’t just mean “adopt C++” (as C++ has a small number of differences from C, but also a C header might want to use such functions).