kickaha: (Default)
[personal profile] kickaha


I've been working on a problem here, and I'll be damned if I'm going to be the only beneficiary of this pain. So here you go: how to convert C++ code to C code, the basics.

Given a class (assume for the minute all public methods and data):
class A {
public:
A();
virtual ~A();
virtual void foo();
};

void
A::foo() {};


You can transform it like so:

Convert the class to a struct, with function pointers for each of the virtual entries.
typedef struct A {
void (*destroy)(struct A*);
void (*foo)(struct A*);
} A;


Declare an external constructor function.
void A__new(A *);

Convert the methods to plain C functions. Each function is given a pointer to an object of the enclosing type... this is 'this', but use 'self' or something else so you don't conflict with any C++ code that might be using this.
void
A__foo(A *self) {}


A destructor can be created in the same way - it handles the breakdown of the internals, and the 'free' is left to the client to do.
A__destroy(A *self) {
}


Create a routine to setup the function pointers, setting them to the C versions.
void
A__fptr_setup(A *self) {
self->destroy = A__destroy;
self->foo = A__foo;
}


Create a 'constructor' (essentially an init). Mallocing is reserved for the client, so it can make an struct of the right size (sub'class'es can be larger), and send it in to be dealt with appropriately here.
void
A__new(A *self) {
A__fptr_setup(self);
}


Now clients can use the above as so:

A* a;
a = (A*)malloc(sizeof(A));
A__new(a);
a->foo(a);
a->destroy(a);
free(a);


The alert reader will realize that the code can be made even more C++-esque by using the following defines:


#define NEW(X, T) X = (T*)malloc(sizeof(T)); T ## __new(X);
#define DELETE(X) X->destroy(X); free(X);

A* a;
NEW(a, A);
a->foo(a);
DELETE(a);


Basically, yes, this is just replicating the C++ functionality in C, manually. But sometimes you're forced to do this for reasons unknown. Trust me. When you least expect it, it'll happen, so now you're armed.

Caveats:
1) Does not enforce or consider protected or private members. You know the drill. Privates get shoved into an opaque pointer dealt with over in the .c file.

2) For classes with a lot of objects being instantiated, consider making the function pointer table its own data struct within the object struct. That way you can have one copy for all instances of a particular class. In my case, there's a small number of objects, so it's quite efficient this way. (Makes the code easier to read too - more like C++... ie, less editing from the original code.)

The good thing about the above approach is that it is more or less doable with search-and-replace in a text editor, with some tweaking.

Now for subclasses...


class B : public A {
public:
void foo();
void bar();
};


Essentially the same as before, but with a few caveats... first off, the function pointer table for A has to be copied in to the struct definition for B. (ew, copy/paste) This is a weak spot, and where the opaque vtable pointer would come in handy, especially for deep inheritance trees or where a small amount of overriding is occurring.

Now B__new looks like:

void
B__new(B *self) {
A__new(self);
B__fptr_setup(self);
// B specific set up
}


And B__destroy looks like:


void
B__destroy(B *self) {
A__destroy(self);
// Any B specific clean up
}


By forcing the external client to set the malloc based on the polymorphic subclass type they want, it ensure that the struct of a big enough size is created.

There. Aren't you glad you read that?

Profile

kickaha: (Default)
kickaha

January 2020

S M T W T F S
   1234
5678 91011
12131415161718
19202122232425
262728293031 

Style Credit

Expand Cut Tags

No cut tags