Home
Fractals
Tutorials
Books
My blog
My LinkedIn Profile

BOOKS i'm reading

Napoleon Hill Keys to Success: The 17 Principles of Personal Achievement, Napoleon Hill, ISBN: 978-0452272811
The 4-Hour Workweek: Escape 9-5, Live Anywhere, and Join the New Rich (Expanded and Updated), Timothy Ferriss, ISBN: 978-0307465351
The Fountainhead, Ayn Rand, ISBN: 0452273331

Patterns for refactoring C programs with C++ (Part 2 of 2)

Olivier Langlois, IT superstar coach, Dominate LinkedIn Formula author
by Olivier Langlois

Contents





Introduction

In 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) structure

This 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 interfaces

The 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 singletons

They 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 initService() and cleaned up in shutdownService(). If you fail to identify the singleton, you might provide a C++ implementation of service.c by replacing its global variable with global C++ objects. This is not the best form that the module could take because you should strive to keep the number of C++ global objects to a minimum for several reasons (no guarantee about the order of initialization of the global objects among many C++ modules, no chance to trap exceptions that the constructors could throw, etc...). Instead, create a singleton class and put all the module global variables into it. Example:

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 init() and shutdown() to your singleton class that will be called from initService() and shutdownService() C functions.

Conclusion

These 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

History

  • 09-14-2007:
    • Original article.


Back to the tutorials list

Home :: Fractals :: Tutorials :: Books :: My blog :: My LinkedIn Profile :: Contact