BOOKS i'm reading |
Patterns for refactoring C programs with C++ (Part 2 of 2)Contents
IntroductionIn the first part of the serie, I presented guidelines that needs to be followed when someone wish to use C++ from C programs. In an ideal world, the old C programs that you want to port to C++ should be rewritten completely. However in the real world, due to economical constraints, this is usually impossible and a more gradual approach must be adopted. During the transition period, to maximize the benefits of using C++ and to not end up with having a program consisting of just a bunch of C interfaces with C++ implementations, you can identify some patterns in the C program and refactor them appropriately. By having gone through the exercise myself, I am presenting the techniques that I have discovered and I will be discussing the benefits and the potential pitfalls of these refactoring patterns.
1. Extend the pimpl idiom to all the data of the C POD (plain old data) structureThis is a continuation of the tip #4 presented in the first part of this serie. That tip presented a way to store C++ objects into a C structure. The problem with C structures is that they are anything except a medium for encapsulation. If the C structure data members are accessed from many places in the code. It can lead to nightmares trying to fix all sort of bugs (heap corruption, memory leak, etc...) related to the access of these structures. The pimpl idiom is also known as the handle pattern. An example of it is the functions of the C standard I/O manipulating a pointer of type FILE. Here is an example of applying the pattern: Before:A.h #ifdef __cplusplus extern "C" { #endif typedef struct { int c; /* is accessed from everywhere in the C modules * Now spend hours to scan thousands of line of C code * to find out why c value is 22 when you expected it * to be 21!!! */ void *m_pimpl; /* Contains a std::map object */ } A_t; A_t *CreateA(); void DestroyA( A_t * ); void DoSomethingWithA( A_t * ); #ifdef __cplusplus } #endif After:A.h #ifdef __cplusplus extern "C" { #endif typedef struct { /* the c var has been moved into the pimpl. */ void *m_pimpl; /* Contains a std::map object */ } A_t; A_t *CreateA(); void DestroyA( A_t * ); void IncC( A_t * ); void DoSomethingWithA( A_t * ); #ifdef __cplusplus } #endif Benefits:Enforce encapsulation on C structures Drawback:This adds overhead by adding a function call everywhere 'c' was accessed. This should only be a temporary situation since the ultimate goal is to totally port the C code into C++. For sections where performance is critical, the next refactoring pattern address this issue and only necessitates that performance critical modules be ported into C++. 2. Support dual interfacesThe pimpl idiom is applied with a void pointer because C has no notions of classes and objects. C++ modules do not have that limitation and you can expose the pimpl class interface to the C++ users. As more modules are ported to C++, you can slowly deprecate the C API. By taking the example of the previous section, it would look like this: A.h #ifdef __cplusplus extern "C" { #endif typedef struct { void *m_pimpl; /* This is actually a CA object and all the */ /* C functions are doing is call the corresponding */ /* CA methods */ } A_t; A_t *CreateA(); void DestroyA( A_t * ); void IncC( A_t * ); void DoSomethingWithA( A_t * ); #ifdef __cplusplus } #include <map> #include <string> class CA { public: void DoSomething(); void IncC() { ++c; } private: int c; std::map<std::string,int> m_map; }; #endif Benefits:C++ modules using A can now fully exploit C++ features (ie: inlining, default arguments, exception handling, create CA objects locally on the stack). 3. Identify C singletonsThey usually have the form: service.h void initService(); void shutdownService(); void useService(); and in service.c, you would find a bunch of global variables initialized by
service.h #ifdef __cplusplus extern "C" { #endif /* * Now these 2 functions could become empty functions as * the CServiceSingleton constructor/destruction is taking * care of initialization/destruction of the service */ void initService(); void shutdownService(); void useService(); #ifdef __cplusplus } class CServiceSingleton : private boost::noncopyable { public: ~CServiceSingleton(); void useService(); CServiceSingleton &GetInstance(); private: CServiceSingleton(); /* * A bunch of stuff that was previously * initialized by the C functions. */ }; #endif service.cpp #include "service.h" CServiceSingleton &CServiceSingleton::GetInstance() { static CServiceSingleton s_Instance; return s_Instance; } void initService() { } void shutdownService() { } void useService() { CServiceSingleton::GetInstance().useService(); } Benefits:All the benefits of items 1, 2 and 3. Potential pitfall:This will move the execution of the initialization/destruction code from very specific places in the C version to the first use of the service in the C++ version and in some contexts, this may be incorrect. If this is your case, just add 2 functions
ConclusionThese were the refactoring patterns that I have discovered while porting myself C code to C++ and there are certainly much more interesting similar refactoring patterns that remain to be discovered. The important thing to keep in mind is that with a careful planning of porting C code to C++, benefits can be obtained almost immediately. Also, my blog is probably the best medium if you would like to provide feedback to this tutorial, you can do so here. Bibliography
|