📜 ⬆️ ⬇️

Object oriented C

You were given the task to write a program in C, and you have already forgotten how a program can work, in the text of which there is not a single word class or virtual? Or maybe you are in love with the simplicity and severity of ANSI C, but sometimes you lack the object-oriented properties of higher-level languages? Or is it just interesting to look at the good old C from a slightly different side? In any case, in this article I will show a few simple techniques, with the help of which you can think and write object-oriented in C.

Suppose we need to write a function to compress data. Data can be compressed by different algorithms. When changing algorithms, the code of the calling function should not change. It is also desirable that the algorithms can be changed on the fly.
In C ++, it would look something like this:

class compressor { public: virtual void compress(void* data)=0; virtual void uncompress(void* data)=0; virtual void status()const=0; }; class rar_compressor : public compressor {...}; class zip_compressor : public compressor {...}; void do_compress(compressor* c, void* in_data, void* out_data) { c->compress(in_data); c->uncompress(out_data); c->status(); } 


Or so:
')
 template<typename Cmp> void do_compress(Cmp& c, void* in_data, void* out_data) { c.compress(in_data); c.uncompress(out_data); c.status(); } 

Let's try to implement this scheme in C.

 #compressor_interface.h typedef struct compressor_t { // `public` interface void (*compress)(struct compressor_t*, void* data); void (*uncompress)(struct compressor_t*, void* data); void (*status)(struct compressor_t*); // `private` part void* impl_; } compressor_t; 


As you can see, only the open interface is available to the user, all implementation details
hidden behind `impl_`.
We implement specific "classes".
 #rar.h void rar_init(compressor_t* c, int cr); void rar_free(compressor_t* c); 


 #rar.c typedef struct { int compressed_ratio; int error; int time_to_finish; } rar_impl_t; //////////////////////////////////////////////////////////////////////////////// //      #define PREPARE_IMPL(c) \ assert(c); \ assert(c->impl_); \ rar_impl_t* impl = (rar_impl_t*)c->impl_; //////////////////////////////////////////////////////////////////////////////// static void compress(compressor_t* c, void* data) { PREPARE_IMPL(c) printf("RAR: compressor working. Compressed ratio: %d\n", impl->compressed_ratio); } //////////////////////////////////////////////////////////////////////////////// static void uncompress(compressor_t* c, void* data) { PREPARE_IMPL(c) printf("RAR: uncompressor working. Will be finished in %d.\n", impl->time_to_finish); } //////////////////////////////////////////////////////////////////////////////// static void status(compressor_t* c) { PREPARE_IMPL(c) printf("Compressed ratio: %d, error: %d\n", impl->compressed_ratio, impl->error); } //////////////////////////////////////////////////////////////////////////////// //  void rar_init(compressor_t* c, int cr) { assert(c); c->impl_ = malloc(sizeof(rar_impl_t)); rar_impl_t* impl = (rar_impl_t*)c->impl_; //  `private`  impl->time_to_finish = 5; impl->compressed_ratio = cr; impl->error = 0; //  `public`  c->compress = &compress; c->uncompress = &uncompress; c->status = &status; } //////////////////////////////////////////////////////////////////////////////// // void rar_free(compressor_t* c) { PREPARE_IMPL(c) free(impl); } 


The "class" of zip is arranged in the same way, therefore I will not give its code here. The only thing that I would like to emphasize is the "constructors" of these classes, in contrast to member functions, do not have to have the same signature, moreover, there may be several of them for each "class".

And now use code:

 void work(compressor_t* c, void* data_to_compress, void* data_to_decompress) { c->compress(c, data_to_compress); c->uncompress(c, data_to_decompress); c->status(c); } int main() { void* uncompressed_data[DATA_SIZE]; void* compressed_data[DATA_SIZE]; compressor_t rar; rar_init(&rar, COMP_RATIO); work(&rar, uncompressed_data, compressed_data); rar_free(&rar); printf("\n"); compressor_t zip; zip_init(&zip, VOLUMES); work(&zip, uncompressed_data, compressed_data); zip_free(&zip); return 0; } 


An astute reader probably already noticed that, in principle, it was possible not to create a second object compressor_t zip , but to use the existing rar , and moreover, if you wish, we can replace one of the functions of the existing object on the fly. This makes the scheme more flexible than the classical classes, for example, in C ++. Also, you probably noticed that this approach is almost a classic
implementation of the Strategy pattern.

So well, with two of the three components of the PLO (do you remember them by heart, of course?) We figured it out. The last one is inheritance. In principle, inheritance can be implemented in many different ways, I will describe here the one that seems to me the most elegant.

The implementation relies on feature C, which allows you to use anonymous member structures as members of other structures (in gcc, you may need to put the flag "-std = c11" or "-fms-extensions"). Let's write "class", "inheriting" from zip compressor'a

 typedef struct logging_compressor_t { union { struct compressor_t; struct compressor_t base; }; void (*log)(struct logging_compressor_t* lc); void *impl_; //   } logging_compressor_t; 


Now we can access the “base” “class” as directly
 logging_compressor_t lc; lc.compress(...) 

so and through the data member
 lc.base.compress(...) 


It is possible to use this “class” polymorphically in the existing code as follows.

 work(&lc.base, uncompressed_data, compressed_data); 


Of course, the meticulous reader will object to me, in such a scheme a lot rests on the accuracy of the programmer. For example, you can spoil everything by calling “destructor” zip for rar and vice versa, and do a lot of other nonsense. But I want to remind you that, firstly, the C-programmer must be careful, and secondly the purpose of the article was not to show that C is “cooler” than other languages, but only that, if desired, you can successfully use Concepts from higher level languages ​​to support a variety of programming styles.

Source: https://habr.com/ru/post/205570/


All Articles