Click here to Skip to main content
15,867,453 members
Articles / Productivity Apps and Services / Microsoft Office / Microsoft Access

Advanced Classes in C

Rate me:
Please Sign up or sign in to vote.
4.87/5 (13 votes)
29 Jun 2013CPOL6 min read 26.7K   29   10
An example of a clean way to implement classes in C

Introduction

This article discusses how to cleanly write advanced classes in C.

This is a method I came up with that allows you to create advanced classes in C very cleanly and easily. It's very useful for large projects that need a lot of structure, especially when working on embedded systems that don't have a C++ runtime (or if you just have a beef with C++ in general).

Background

I've fiddled around with this a lot in the past, but it was always very amateurish at best. However, I was writing a post in my blog about programming tricks I've learned over the years in C and the page formatting just didn't do it justice. Besides, CodeProject has helped me overcome numerous obstacles in the past and I felt like giving back, you know?

Using the Code

The first thing that needs to be done is to define a few definitions and macros to make the definition of classes easier. The macros use memcpy and malloc / free, so you'll need to include the following headers:

C++
#include <string.h>	// for memcpy
#include <malloc.h>	// for malloc / free   

The first definitions I'll cover are used to define the class types.

C++
#define class				typedef struct
#define class_t				void *
#define ref_class			static struct
#define partial_class			struct 

The first definition is used to define normal classes. Unfortunately, because of the way structs work in C, the name will have to be after the class definition. It's annoying, but oh well.

The second definition defines the class type which is returned by the constructors, destructors, and operators of the classes.

The third definition defines a reference class. Reference classes have 2 purposes. First, whenever a new class is created, it duplicates the reference class to initialize the pointers to the functions. The second is to provide access to protected members not contained within the class itself in pretty much the same way you'd use a static member function in a C++ class. The reasoning behind this is that you wouldn't really want to duplicate 20-30 pointers to functions every time you create a class. You'd instead make them protected (members of the reference class) so they're still accessible since they never change.

The fourth definition defines partial classes which are pretty much only used to define the private members of a reference class.

The next definitions are the access modifiers.

C++
#define public				{
#define private				}, {
#define protected			},

You're probably wondering what's going on here, but it'll go without explanation later. The only thing that should be said is that there should always be at least a public and protected section in all your reference classes, and the order should always be public, private, then protected.

The next macros are used to declare the constructor, destructor, operators, and members of a class.

C++
#define constructor(...)		class_t (*_constructor)(class_t, ##__VA_ARGS__)
#define destructor(...)			class_t (*_destructor)(class_t, ##__VA_ARGS__)
#define operator(o)			class_t (*op_##o)(class_t, ...)
#define member(type, name, ...)		type (*name)(class_t, ##__VA_ARGS__)

The first and second macros are used to declare the class has a constructor or destructor, and the parameters are the parameters to be passed to the functions.

The third macro is used to declare an operator and the parameter is the name of the operation such as 'add', 'sub', 'mul', etc. It adds an 'op_' prefix to the name and declares it as having 1 or more parameters.

The fourth macro is used to declare a member function and the parameters are the function type, name, and parameters the function takes.

The next macros are used to define the constructor, destructor, operators, and members of a class.

C++
#define constructor_set(...)		
	._constructor = (class_t(*)(class_t, ##__VA_ARGS__))
#define destructor_set(...)		
	._destructor  = (class_t(*)(class_t, ##__VA_ARGS__))
#define operator_set(o)			
	.op_##o       = (class_t(*)(class_t, ...))
#define member_set(type, name, ...)	
	.name = (type (*)(class_t, ##__VA_ARGS__))

The first and second macros are used to set the constructor and destructor of the class, and the parameters are the parameters of the function. Immediately after the use of this macro should be the name of a function, for example:

C++
constructor_set() MyClass_constructor    

This applies to the other 2 macros as well. The third macro is used to define an operator and the parameter is the operation name.

The fourth macro is used to define a member function, and the parameters are the function type, name, and parameters of the function.

The next macros are used to create a new class and optionally call the constructor.

C++
#define new(type, ...)			
(type *)((type *)&_##type)->_constructor(memcpy(malloc
(sizeof(type)), &_##type, sizeof(type)), ##__VA_ARGS__)
#define new_(type) (type *)		
memcpy(malloc(sizeof(type)), &_##type, sizeof(type))
#define _new(type, name, ...)		
(type *)memcpy(&(name), &_##type, sizeof(type)); 
name._constructor(&name, ##__VA_ARGS__)
#define _new_(type, name)		
(type *)memcpy(&(name), &_##type, sizeof(type))

The macros with a '_' suffix do not call the constructor on creation, regardless of whether or not it exists.

The macros with a '_' prefix do not allocate member for class with malloc, and instead just memcpy the reference class into an existing one. The parameters for these are class type, class object (not pointer), and parameters passed to the constructor.

For the macros without a '_' prefix, the parameters are just class type and parameters to pass to the constructor.

The next macros are used to delete a class and optionally call the destructor:

C++
#define delete(name, ...)		
free(name->_destructor(name, ##__VA_ARGS__));
#define delete_(name, ...)		free(name);
#define _delete(name, ...)		name._destructor(&(name), ##__VA_ARGS__);
#define _delete_(name, ...)

The concept is the same as the new macros; '_' prefixed ones are for objects with no freeing needed, '_' suffixed ones don't call the destructor.

Now that I've finally covered the usage of all the macros, I'll explain the process of class creation.

  1. Declare the class
  2. Declare the functions
  3. Define the class
  4. Define the functions

Class creation must always be done in this exact order, otherwise you'll get undefined reference errors. The next snippet shows how to declare a class:

C++
class {
  constructor();
  destructor();

  const char *helloText;
  const char *worldText;

  member(int, print, char *);
  member(int, println, char *);
} MyClass;

You can see that despite the seemingly complex macros, this is actually fairly easy to understand. Next is step 2, declaring the functions.

C++
class_t MyClass_constructor(MyClass *this);
class_t MyClass_destructor(MyClass *this);

class_t MyClass_op_add(int lop, int rop);

void MyClass_print(MyClass *this, char *text);
void MyClass_println(MyClass *this, char *text);

Note that the constructor, destructor, and operators have a class_t return type. Also, it's important to note that for the sake of this example, the operator function is using integer arguments which may output a compiler warning. Normally, they'd also be class_t.

Next is step 3, defining the class:

C++
ref_class {
  MyClass MyClass;

  partial_class {
     int izSecret;
  } _MyClass;

  operator(add);
} _MyClass = {

public
  constructor_set()	MyClass_constructor,
  destructor_set()	MyClass_destructor,

  .helloText = "Hello ",
  .worldText = "World",

  member_set(int, print, char *)	MyClass_print,
  member_set(int, println, char *)	MyClass_println,


private
  .izSecret = 5,


protected
  operator_set(add) MyClass_op_add,

};

Now if this isn't sexy, I don't know what is.

First thing's first, the name of the reference class should be identical to the name of the class, but with a single '_' prefix, and the very first member should always be the same type as the class. This is because the reference class extends the class with private and protected members.

It's optional, but if they do exist, the private members must be within a partial class, and they must be the second member of the reference class. For the sake of continuity, you can see I named the first member MyClass to illustrate that part is public and visible by the MyClass class and named the second member _MyClass to illustrate that part is private and visible only by the _MyClass reference class.

After declaring the first and (optional) second members, the rest are declared just like you would any normal class members before closing and moving on to the actual definition.

Here you can see the magic of the public, private, and protect definitions at work, being translated to brackets to define the public and private classes; and at the same time giving an almost identical appearance to C++ access modifiers.

Aside from the usual definitions for everything done by the macros, you can see I used '. helloText' and '.worldText' to define those. It's not required if you define everything in the same order it was declared, but I still highly suggest doing so anyway in case you need to make any changes later on.

Then lastly we do step 4, defining the functions.

C++
class_t MyClass_constructor(MyClass *this) {
   printf("constructor was called\n\n");
   return this;
}

class_t MyClass_destructor(MyClass *this) {
   printf("\ndestructor was called\n");
   return this;
}

class_t MyClass_op_add(int lop, int rop) {
   printf("%i\n", lop + rop);
   return NULL;
}

void MyClass_print(MyClass *this, char *text) {
  printf("%s", text);
}

void MyClass_println(MyClass *this, char *text) {
  printf("%s\n", text);
}  

It's important to note here that the constructor and destructor functions must return 'this'.

Now, an example usage:

C++
int main(int argc, char *argv[]) {
  MyClass _mc, *mc = _new(MyClass, _mc);	// create the 
  				// class and call the constructor

  mc->print(mc, (char *)mc->helloText);// call member function

  _delete_(_mc)				// delete the class


  mc = new_(MyClass);			// create allocated class

  mc->println(mc, (char *)mc->worldText);	// call member function
  _MyClass.op_add(4, 5);		          // perform operation

  delete(mc);				// delete and free class, call destructor

  return 0;
}

That's all there is to it!

Points of Interest

While I was writing the code, the operator macro originally took an actual operator like 'operator(+)', which was done mathematically by checking a bunch of conditions against the operator and determining which function to call. The problem though is that it required all operators to be defined and did nothing more than slow down processing, so it was tossed. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionGlad to see people still using C. Pin
Jeremy Falcon31-Jan-16 8:00
professionalJeremy Falcon31-Jan-16 8:00 
QuestionAre there virtual functions and late binding? Pin
Sergey Alexandrovich Kryukov23-Nov-13 11:45
mvaSergey Alexandrovich Kryukov23-Nov-13 11:45 
AnswerRe: Are there virtual functions and late binding? Pin
Ghosuwa Wogomon23-Nov-13 12:05
Ghosuwa Wogomon23-Nov-13 12:05 
GeneralRe: Are there virtual functions and late binding? Pin
Sergey Alexandrovich Kryukov23-Nov-13 12:12
mvaSergey Alexandrovich Kryukov23-Nov-13 12:12 
GeneralRe: Are there virtual functions and late binding? Pin
Ghosuwa Wogomon23-Nov-13 13:31
Ghosuwa Wogomon23-Nov-13 13:31 
This tutorial doesn't cover it, but it does support it. You just have to locate the functions yourself and overwrite the members for them in the base class. If you wanted it to be runtime-level, you could add the constructor attribute to the function that locates them so it's called before the program's main() function.

The new technique I came up with resolves them in the new function rather than copying from a base class and uses a sub function to hide the "this" argument.
GeneralRe: Are there virtual functions and late binding? Pin
Sergey Alexandrovich Kryukov24-Nov-13 11:51
mvaSergey Alexandrovich Kryukov24-Nov-13 11:51 
GeneralRe: Are there virtual functions and late binding? Pin
Ghosuwa Wogomon24-Nov-13 15:26
Ghosuwa Wogomon24-Nov-13 15:26 
GeneralRe: Are there virtual functions and late binding? Pin
Sergey Alexandrovich Kryukov24-Nov-13 16:03
mvaSergey Alexandrovich Kryukov24-Nov-13 16:03 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA13-Jul-13 20:41
professionalȘtefan-Mihai MOGA13-Jul-13 20:41 
Bugmalloc.h is non-portable Pin
Chad3F3-Jul-13 10:15
Chad3F3-Jul-13 10:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.