Templated Pimpl |
|
1.1.3.1 MotivationThe "Pimpl" idiom (see a thorough description in the guru of the week) is a very useful technique to hide away the private section of a class (i.e., what should go unconditionally into the implementation file instead of the header file, were it not for the lack of consistency in the syntax and abilities of C++). Such implementation hiding has, at least, the following advantages:
The first advantage is important because it makes your code more modular and reusable. Your code should be useable and reusable by looking only at the header files, which constitute the "software contract" of your classes and functions. If an user of your code cannot gather all the information he needs from the declarations and definitons and the accompanying comments in your header files, and has to resort to digging in your implementation files, then you have failed in your pursuit of modularity. On the other hand, not cluttering your "software contract" with details that do not belong into it will make it clearer and bring your users closer to enlightenment. The second advantage results in reduced compilation times, and fewer files to recompile after a source code modification. It can also remove incidental compile-time cyclic dependencies. 1.1.3.2 ImplementationHaving coded too many "Pimpl" devices "by hand", I have tried to extract the essence of the "Pimpl" idiom and turn it into a C++ module. Here is my small contribution to C++ elegance: the templated "pimpl". 1: // file "pimpl.h"
2: #ifndef PIMPL_HEADER_
3: #define PIMPL_HEADER_
4:
5: template <typename Owner, int part=0>
6: class Detail {
7: public:
8: class Implementation;
9: Detail(Implementation *implementation)
10: : implementation(implementation) { }
11: ~Detail()
12: { delete implementation; }
13: Implementation *operator->() const
14: { return implementation; }
15: private:
16: Implementation *const implementation;
17: };
18:
19: #endif
Now, what does this small piece of code buy us? First, it allows us to think about the implementation, express it, and be read by fellow programmers at a higher level of abstractness. It is good to explicitly and unambiguously state that we are using this idiom, rather than having the human reader guess it; it can also show the way to people that do not know the idiom without having to document it explicitly. Second, it automatically removes the possibility of memory leaks in a single place (have you never forgotten to delete a "Pimpl"?). And that's all; as I said, a small contribution, nada to be gained in terms of efficiency or number of lines of code, but definitely a step forward for mankind. 1.1.3.3 How to use itIf you want to use the templated "pimpl" to declare a "pimpl" for a
class named
If for some reason you have several "pimples" for the same class, use
the second template argument of Here is an simplistic example that will hopefully make things clearer.
The header file defines the classes 1: // file: "pimltest.h"
2: #ifndef PIMPLTEST_HEADER_
3: #define PIMPLTEST_HEADER_
4:
5: #include "pimpl.h"
6:
7: class A {
8: public:
9: A(int i);
10: ~A();
11: int get_i() const;
12: private:
13: Detail<A> implementation;
14: };
15:
16: class B {
17: public:
18: B(int i, double k);
19: ~B();
20: double get_i_times_k() const;
21: private:
22: Detail<B, 1> implementation_1;
23: Detail<B, 2> implementation_2;
24: };
25:
26: #endif
The implementation file defines the "pimples", and the implementations of
A and B access it:
1: // file: "pimltest.cpp"
2: #include "pimpltest.h"
3:
4: template <>
5: class Detail<A>::Implementation {
6: public:
7: int i;
8: };
9:
10: A::A(int i)
11: : implementation(new Detail<A>::Implementation) {
12: implementation->i=i;
13: }
14:
15: A::~A() { }
16:
17: int A::get_i() const {
18: return implementation->i;
19: }
20:
21:
22: template <>
23: class Detail<B, 1>::Implementation {
24: public:
25: int i;
26: };
27:
28: template <>
29: class Detail<B, 2>::Implementation {
30: public:
31: double k;
32: };
33:
34: B::B(int i, double k)
35: : implementation_1(new Detail<B, 1>::Implementation),
36: implementation_2(new Detail<B, 2>::Implementation) {
37: implementation_1->i=i;
38: implementation_2->k=k;
39: }
40:
41: B::~B() { }
42:
43: double B::get_i_times_k() const {
44: return implementation_1->i*implementation_2->k;
45: }
Just to make sure that everything works, here is a test:
1: // file: "pimpltestmain.cpp"
2: #include <iostream>
3: #include "pimpltest.h"
4:
5: int main() {
6: A a(7);
7: std::cout << a.get_i() << std::endl;
8: B b(2, 3.4);
9: std::cout << b.get_i_times_k() << std::endl;
10: }
and here is what it prints:
1: 7 2: 6.8 |
|||||||||||||||||||||||||||||||||||||||||||||||||