Object Oriented Programming in C

in #programming7 years ago

Object Oriented Programming in C

It is a reality of modern programmers' life that the vast array of programming languages available to them in this day and age supports object oriented features.

Object oriented software design is widely adopted across most industries and is supported by an army of Java, Python, C# and similar language developers.

Yet the C procedural programming language still dominates the development of software aimed to run close the underlying hardware.

[Image Credit - Wikipedia]

Operating system kernels are still widely implemented in the C programming language, so are applications running on embedded systems.

The reasons for this is how closely the instructions available to the language map to the underlying hardware (e.g. CPU) and the fact that programs developed in C are compiled into executables which can run without the need for an external interpreter.

If designed properly, C programs are lean and fast and offer complete control to the programmer as to how they want to use the resources offered by the kernel (such as stack or heap memory).

Yet C does not offer exact object oriented features such as classes and inheritance.

The closest object-oriented language relative to C which, it can be argued, can offer similar performances, is C++.

C++ is like C's youngest brother. C++ supports all C features and syntax with the addition of a plethora of features which turns it object-oriented and much much more.


[Image Credit: CERN]

I have recently volunteered to participate in @lemouth's experiment to get some programmers in the Steemit community involved in developing open source software for particle physics.

This project makes use of the MadAnalysis5 package which provides features for helping analyze particle collision events in accelerators such as CERN.
MadAnalysis5 provides, among other things, C++ libraries which can be used for building advanced applications.

So it looks like I'll be doing a little bit of C++ programming on the sideline. :-)

This prompted me to think about how I have been using the C programming language over the years and how it would compare to using C++.

Truth is that I'm not a massive fan of C++ for the reason that I believe that it adds a huge amount of features to C that are not really essential.

My experience with C++ is that it is used too often by programmers who are not experienced enough with software design which result in a mess that is extremely hard to debug and improve.

Don't get me wrong, it's easy to make a mess when programming in C as well, but making sense from a large C program tends to be much, much easier than the C++ equivalent where many of the language features have been used.

In my mind, one of the worst culprit of the C++ language, though adored by most C++ programmers, is the concept of templates.

Frameworks and libraries using C++ templates can be very hard to read, extremely slow to compile and result in the most cryptic debugger messages.

Key Advantage of C++ - Type Safety

The main benefit of C++ templates is that they provide the programmer with a way to develop generic code with full type safety.

For example, if a C programmer wants to implement some kind of generic container library, he/she would do something like this:

typedef struct
{
    ...
} my_super_duper_container_t;

void add_elem (my_super_duper_container_t* container,
                void* elem);

Clearly the above code fragment does not enforce the type of the elements being added to the container.

The use of C++ allows the compiler to enforce that elements added to an instance of the container are of a specific type.

But in my mind the type safety gain of using templates does not warrant the cost of increase in syntax complexity and all the other issues listed above.

One just needs to compare the C++ STL library to the glibc library to start comprehending the difference in complexity.

Object Oriented Programming

What about the main subject of this article regarding object oriented programming?

In this post I review the key attributes of object oriented design: encapsulation, inheritance and polymorphism.

Though my company has been using C mostly for many years, our software design is very much object oriented.

Let's examined how this is typically achieved.

Encapsulation

In C++ (like all object oriented languages), objects are defined as classes.
In C, objects can be defined as struct.

Note that there is very few differences between a class and a struct.
The only significant difference lies in the build in protected and private mechanisms in C++ classes.

Let's see how private methods/members can be achieved in C.

Here is a typical class declaration in a header file in C++:

class MyClass
{
public :
    MyClass (list of args);
    void DoSomething (list of args);
    int DoSomethingElse (list of args);
protected:
    void DoSomethingProtected (list of args);
private:
    void DoSomethingPrivate (list of args);
    
protected :
    list of attributes;
private :
    list of attributes;
};

Let's now have a look at similar example in a C header file:

typedef struct my_class_t
{
    list_of_attributes
} my_class_t;

my_class_t* instanciate_my_class (list of args);
void do_something (my_class_t* instance, list of args);
int do_something_else (my_class_t* instance, list of args);

Now you probably notice that C doesn't have the concept of protected or private methods/attributes.
The way to do this in C is to separate what is private from what is public by declaring the functions / variables between header and source files.
This uses the convention that "class" implementation in C is usually separated between public declaration (in the header file) and private declaration / function definition (in the source file).

With the above example:

my_class.h:

struct my_class_t;

struct my_class_t* instanciate_my_class (list of args);
void do_something_public (struct my_class_t* instance, list of args);

my_class.c

typedef struct my_class_t
{
    list of attributes;
} my_class_t;

struct my_class_t* instanciate_my_class (list of args)
{
    ...
}

void do_something_public (struct my_class_t* instance, list of args)
{
    ...
}

void do_something_private (struct my_class_t* instance, list of args)
{
    ...
}

Notice one thing in the above. The C++ code must define in the header file all methods and attributes, even the protected / private ones.

The C header file is cleaner because it only contains those methods that are public. Everything else is "hidden" in the source file.

Inheritance and Polymorphism

Now, how would you implement inheritance and polymorphism in C?

In C++ you can derive classes, override methods and make the "same" method behave differently between parent and derived class in a way that is opaque to the user code.

This can all be achieved easily in C as well. Let's see how.

Let's take a C++ example (for the sake of simplicity I put some method definition in the class declarations).

parent.h:

class Parent
{
public :
    Parent (list of args);
protected :
    virtual void do_something (list of args)
    {
        cout << "This is the parent" << endl;
    }
    
    list of parent attributes;
};

child.h:

class Child : Parent
{
public :
    Child (list of args);
protected :
    void do_something (list of args)
    {
        cout << "This is the child" << endl;
    }

    list of child attributes
};

Now one can instantiate Child and invoking do_something will obviously print out "This is a child".

Child child(...);
child.do_something(...);

Result:
This is the child

The nice thing here is that a Child instance can be passed to some user code as a Parent instance.
The user code doesn't need to be aware that the instance is actually of type Child.
Instead it uses the Parent interface which actually result in the Child method to be invoked:

void execute (Parent& object)
{
    object.do_something(...);
}

Child child (...);
execute(&child);

The above code will actually display "This is a child".

How would you do this in C in an object oriented way?
This is how it can be done:
parent.h:

typedef struct parent_t
{
    void (*do_something) (parent_t* object, list of args);

    list of parent attributes
} parent_t;

void init_parent (parent_t* parent);

child.h:

#include "parent.h"

typedef struct child_t
{
    parent_t    parent;
    
    list of child attributes;
} child_t;

child_t* instantiate_child (list of args);

This is how the parent and the child code is tied together:
parent.c


static void parent_do_something (parent_t* parent, list of args)
{
    printf("This is the parent\n");
}

void init_parent (parent_t* parent)
{
    parent->do_something = parent_do_something;
}

child.c:


static void child_do_something (parent_t* parent, list of args)
{
    child_t* child = (child_t*)parent;

    printf("This is the child\n");
}

child_t* instantiate_child (list of args)
{
    child_t* child;

    child = malloc(sizeof(*child)));
    init_parent((parent_t*)child);
    child->do_something = child_do_something;

    return child;
}

As you can see in the above code, the structure definition child_t has a parent_t attribute as first member.
This means that a child_t instance can be casted to parent_t
The instantiate_child function overrides the do_something member function which result in the same behavior as the C++ code above.

The following code:

void execute (parent_t* parent, ...)
{
    parent->do_something(parent, ...);
}

child_t* child = instanciate_child(...);
execute((parent_t*)child);

works and will display "This is the child".

So the small code snippets above shows how basic inheritance and polymorphism is achieved in C.
The object oriented principles can easily be transferred to a procedural language like C.

Conclusion

Programming is programming at the end of the day and I tend to worry more about design than the used programming language.

In this post I showed how object oriented design can be achieved with a non-object oriented language such as C.

I hope that this will be useful to other programmers.

References

Sort:  

Interesting article. But saying that C is easier to debug and maintain, and then giving "polymorphism" example relying on elements order in a struct is a bit overstretch :)

Hi @mactro.
I understand where you are coming from.
I guess that this is very common in C and preserving element order between parent struct and derived struct is exactly what the C++ compiler does for inheritance. :-)
It's not very complicated but it forces the programmer to think about how information is stored in memory.
Thanks for your feedback.

Thanks for the free advertisement to the MA5 project :)

And nice post by the way ;)

By the depth of this article, you have demonstrated a good knowledge of programming in general. However, I am. No fan of this and all sounding foreign to me. I do hope those that are skilled in programming will certainly find your article very helpful.

@eurogee

Excellent article. I am a university professor at UDONE, Venzuela and one of the contents is Object Oriented Programming, initiating this programming paradigm is complex to understand for students who are just beginning their career; However, with effort and dedication good results are achieved. You have explained in a simple way to understand that I will take it as a reference for my classes. Undoubtedly the C ++ programming language is the ideal option to teach Object Oriented Programming.
Thank you.

Coin Marketplace

STEEM 0.21
TRX 0.25
JST 0.039
BTC 95276.42
ETH 3297.99
USDT 1.00
SBD 3.16