C++ in theory: Bridging Your Classes with PIMPLs
(Page 1 of 4 )
Very often, when a program takes longer to compile after you have made what appear to be trivial changes, the blame can be laid at the door of dependency chains between header files. One change can trigger the need for a massive rebuild. J. Nakamura explains a way to make header files insensitive to any change -- thus saving all that rebuild time -- by using pimpl.
Looking For Problems
Large development projects can become a drain... not just on your brain, but on your valuable time as well. When you notice increasing compilation time after making what you thought to be trivial changes, you can claim to have found a problem (some might say that you are looking for one). The application might run fine and be without bugs, but those increasing build times do not really speed up the development process. They are also very annoying when you are making small changes in a debug cycle.
Most often the cause for these extreme build times can be found in large dependency chains between your header files. Consider the following header files:
// MyBase.h
class MyBase {
public:
int foo();
};// MyDerived.h
#include “MyBase.h”
class MyDerived : public MyBase {
public:
int bar();
};
If you make a change to MyBase.h (let's assume you need a new private or protected member variable), MyDerived and everything, including its header file, will have to be recompiled as well. You can imagine that in a large project, many header files will be dependent on each other, often many levels deep. At a certain point you will notice that you are becoming reluctant to make changes to a class, simply because these changes will trigger a massive rebuild (and might evoke some unpleasant primitive reactions in your colleagues).
There are tools available to analyze these dependencies. Personally I prefer to use Doxygen, which is used to generate documentation from sources. Doxygen can use ‘dot’ from GraphViz to generate nice pictures of header file dependencies. Best of all, it is free!
There are ways to minimize the dependency problem -- for example, by forbidding coders to include header files in header files! But even so, your changes to MyBase.h will trigger a rebuild of MyDerived.cpp, since that is the file that includes the MyBase header now.
Wouldn't it be nice to be able to make the header file insensitive to any change? We would like to prevent recompilation when making changes to a private interface. This is where we use pimpl for [Sutter].
C++ in theory: Bridging Your Classes with PIMPLs - The Private Implementation The
private implementation is a variation of the Bridge Pattern [Gamma],
which is intended to decouple an abstraction from its implementation,
so that the two can vary independently. When you take a look at
the C++ header and class declaration, you will notice that both the
public and private interface is usually declared at the same location.
Conceptually this is quite strange: in effect, the class lets you peek
at its private parts, which should be of no concern to you. So let's
hide it! // MyClass.h The
only visible private member variable in our class is the pimpl. We
defined a forward declaration of the private implementation class; we
don’t need any further information about it. The compiler needs to know
how much memory to reserve for MyClass. Since we are only storing a
pointer (which on my win32 platform is 4 bytes big), the compiler
doesn’t care what the memory layout of MyClassImpl looks like. Unless you make changes to the public interface, this header file is unlikely to change! Lets take a look at the implementation. // MyClass.cpp MyClass::MyClass() MyClass::~MyClass() int MyClass::foo() The
implementation class is defined in our source file, literally confined
to the source implementation of our class. Any changes to our
implementation class (the introduction of new member variables and
member functions, for example) will trigger no recompilations outside
this source file. Basically all public functions redirect the
call to the function in the private implementation when you implement a
pimpl, although this is not strictly necessary. MyClass::foo() could
also be implemented as: int MyClass::foo() { I
consider it to be more effective and cleaner to just redirect the call
to the pimpl. It also saves you multiple pointer dereferences. The
destruction of the pimpl is wrapped in a try-catch block, because we
don’t want any exceptions to escape our own destructor. We do this
because, when MyClass is destroyed by the exception-handling mechanism
during the stack-unwinding part of exception propagation, any
exceptions triggered during this destruction will force C++ to call the
terminate function [Meyers]. Immediate termination of your application
is one of the worst things that can happen to it! C++ in theory: Bridging Your Classes with PIMPLs - Pimpl Drawbacks You do pay a price for pimpls when it comes to performance. Let's set up some code: #include <stdio.h> class MyClassA { class MyClassB { int main(int argc, char *argv[]) Each construction/deconstruction needs to allocate/free memory. Since
the implementation is hidden in a separate class behind a pointer,
every time our pimpled class is created, we need to allocate memory on
the heap. And every time our pimpled class is destroyed, we need to
free memory. Compared to common operations like function calls, memory
allocation and deallocation are relative expensive operations. Normally
you would not notice or worry about this performance cost, but when you
have to construct/destruct an array of your pimpled object repeatedly,
you will notice a difference: start = clock(); start = clock(); clock()
calculates the processor time used by the calling process; we can use
this to measure how much time is spent on constructing and destructing
MyClassA arryA[0xffff] and MyClassB arryB[0xffff] 10 times. I prefer to
cast the result of printf to void (it returns the number of characters
printed or a negative value if an error occurs), just to make it clear
that we are ignoring the return value. When you run the sample,
you will notice that the construction of the pimpled class takes a
lot more time than the non-pimpled one. On my PIV 3Ghz the result was: ticks spent on MyClassA: 31 Though
the sample might be a bit extreme, you can see that the difference is
not trivial. Remember that the construction of an n-array of objects,
constructs n objects on the stack for you. In this case it means that
the MyClassB constructor is called n times, performing n allocations on
the heap! To make things worse, this example doesn’t do anything
with the array, and since it immediately goes out of scope, the
destructor is called n times as well. Access to hidden members comes at the cost of at least one extra indirection. Each
access of a member in the pimpled class can require at least one extra
indirection. A pointer dereference can be quite an extra cost if the
code is called often: MyClassA aObj; MyClassB bObj; Running another extreme sample, my result was: ticks spent calling MyClassA::foo(): 1500 Again, this is not a trivial difference when the code is executed often! Access to public members comes at the cost of an extra indirection. Sometimes
the private implementation needs to access public member functions of
the pimpled class. Storing a “back pointer” to the visible object can
easily do this. By convention, this back pointer is usually named
“self.” // MyClass.h // MyClass.cpp Looking at the previous example, it is clear that an extra indirection back will drive the performance cost even further up. There is a small space overhead. “Space
overhead?” you might ask -- and you might not think this is a big
deal. When you have a lot of small objects which you have pimpled,
however, that extra pointer does start to count (when you need a back
pointer, you will actually need two extra pointers). Try the following
on the test code above: (void)printf(“sizeof MyClassA: %d\n”, sizeof(MyClassA)); Result will vary among compilers but the overhead I have is actually 7 bytes: sizeof MyClassA: 1 Even
though a pointer only costs 4 bytes on my machine, the compiler aligns
it on a 4-byte boundary, wasting 3 bytes (char c takes one byte). The
overhead will be even larger on an AS/400 or 64-bit machine. Of course
you can configure this in your build settings...but that is not my
point. C++ in theory: Bridging Your Classes with PIMPLs - Summary The
pimpl allows you to decouple the interface of your class from its
implementation. On top of that, the implementation is not permanently
bound to the interface. Any compile time dependencies are completely
eliminated with the pimpl. This frees you from the fear to make changes
to the implementation. So use the pimpl when you want to hide the
implementation details of your class. Before you do, however, make
certain that you understand the performance penalties involved. References [Sutter] Exceptional C++ item 26~30. [Gamma et all] Design Patterns – Bridge. [Meyers] More Effective C++ item 11
(Page 2 of 4 )
class MyClassImpl; // forward declaration
class MyClass {
public:
MyClass();
~MyClass();
int foo();
private:
MyClassImpl *m_pImpl;
};
class MyClassImpl {
public:
int foo() {
var*=var;
return bar();
}
int bar() { return var & 0xFF; }
int var;
};
: m_pImpl(new MyClassImpl)
{}
{
try { delete m_pImpl; }
catch (...) {}
}
{ return m_pImpl->foo(); }
m_pImpl->var*=m_pImpl->var;
return m_pImpl->bar();
}
(Page 3 of 4 )
#include <time.h>
public:
MyClassA( ) : c(0) {}
char foo( ) { return c; }
char c;
};
public:
MyClassB( ) : m_pImpl(new MyClassA) { }
~MyClass( ) { try { delete m_pImpl; } catch ( ... ) { } }
char foo( ) { return m_pImpl->foo( ); }
private:
char c;
MyClassA *m_pImpl;
};
Here
we have set up MyClassA to be the private implementation of MyClassB.
We are going to measure how much performance overhead the function
indirection and memory allocation require.
{
clock_t start, finish; // stores time values
for (int count = 0; count < 10; ++count)
{
/* our test code will go here */
}
return 0;
}
for (int i = 0; i < 10; ++i ) MyClassA arryA[0xffff];
finish = clock();
int durationA = finish – start;
(void)printf(“ticks spent on MyClassA: %d\n”, durationA);
for (int j = 0; j < 10; ++j ) MyClassB arryB[0xffff];
finish = clock();
int durationB = finish – start;
(void)printf(“ticks spent on MyClassB: %d\n”, durationB);
ticks spent on MyClassB: 719
start = clock();
for (int k = 0; k < 0xffffff; ++k) (void)aObj.foo();
finish = clock();
durationA = finish – start;
(void)printf(“ticks spent calling MyClassA::foo(): %d\n”, durationA);
start = clock();
for (int l = 0; l < 0xffffff; ++l) (void) bObj.foo();
finish = clock();
durationB = finish – start;
(void)printf(“ticks spent calling MyClassB::foo(): %d\n”, durationB);
ticks spent calling MyClassB::foo(): 3156
class MyClass {
public:
MyClass();
~MyClass();
void foo();
void bar();
private:
MyClassA *m_pImpl;
};
class MyClassImpl {
public:
MyClassImpl(MyClass *_self) : self(_self) {}
void foo() { self->bar(); }
};
MyClass::MyClass()
: m_pImpl(new MyClassImpl(this))
{}
MyClass::~MyClass()
{
try { delete m_pImpl; }
catch (...) {}
}
(void)printf(“sizeof MyClassB: %d\n”, sizeof(MyClassB));
sizeof MyClassB: 8
(Page 4 of 4 )
- DLL Conventions: Issues and Solutions (0)2007/07/27
- More on Handling Basic Data Types (0)2007/07/27
- C++ in theory: Bridging Your Classes with PIMPLs (0)2007/07/27
- C++ In Theory: The Singleton Pattern (0)2007/07/27
- <iostream> vs. <iostream.h> (0)2007/07/03

수안이의 컴퓨터 연구실




Leave your greetings.