在读《Effective C++》和项目源代码时,看到pImpl Idiom。它可以用来
降低文件间的编译依赖关系,通过把一个Class分成两个Class,一个只提供接口,另一个负责实现该接口,实现接口与实现的分离。这个分离的关键在于“以声明的依赖性”替换“定义的依赖性”,而编译依赖性最小化的本质是:让头文件尽可能的自我满足,万一做不到,则让它与其他文件内的声明式(而非定义式)相依。
引用
这里的一些描述:
The Pimpl idiom, also known as the compilation firewall or Cheshire Cat
technique, is a "private implementation" technique useful only in CeePlusPlus and statically compiled languages like it...
Benefits:
- Changing private member variables of a class does not require recompiling classes that depend on it, thus make times are faster, and the FragileBinaryInterfaceProblem is reduced.
- The header file does not need to #include classes that are used 'by value' in private member variables, thus compile times are faster.
- This is sorta like the way SmallTalk automatically handles classes... more pure encapsulation.
Drawbacks:
- More work for the implementor.
- Doesn't work for 'protected' members where access by subclasses is required.
- Somewhat harder to read code, since some information is no longer in the header file.
- Run-time performance is slightly compromised due to the
pointer indirection, especially if function calls are virtual (branch
prediction for indirect branches is generally poor).
How to do it:
- Put all the private member variables into a struct.
- Put the struct definition in the .cpp file.
- In the header file, put only the ForwardDeclaration of the struct.
- In the class definition, declare a (smart) pointer to the struct as the only private member variable.
- The constructors for the class need to create the struct.
- The destructor of the class needs to destroy the struct (possibly implicitly due to use of a smart pointer).
- The assignment operator and CopyConstructor need to copy the struct appropriately or else be disabled.
Code:
1 struct AImp;
2 class A {
3 public:
4 // Same public interface as A, but all delegated to concrete implementation.
5 private:
6 AImp * pimpl;
7 };
8
If you use a SmartPointer
and you only have one implementation, there is no need to make any of
the member functions virtual, except possibly the destructor. The
run-time cost of non-virtual member function calls is much lower, and a
compiler that does whole-program optimization can inline them even
though they're in a separate translation unit. Here's an example:
1 // foo.h
2
3 class foo_impl;
4
5 class foo {
6 // Boilerplate
7 friend class foo_impl;
8 foo() {} // so only foo_impl can derive from foo
9 const foo_impl * impl() const;
10 foo_impl * impl();
11 public:
12 virtual ~foo() {}
13 // Factories
14 static std::auto_ptr<foo> create(int value);
15 // Interface
16 int value() const;
17 };
18
19 // foo.cpp
20
21 class foo_impl : public foo {
22 friend class foo;
23 // Constructors mirroring the factory functions in foo
24 explicit foo_impl(int value) : value_(value) {}
25 // Member data
26 int value_;
27 };
28
29 inline const foo_impl * foo::impl() const {
30 return static_cast<const foo_impl *>(this);
31 }
32 inline foo_impl * foo::impl() {
33 return static_cast<foo_impl *>(this);
34 }
35
36 std::auto_ptr<foo> foo::create(int value) {
37 return std::auto_ptr<foo>(new foo_impl(value));
38 }
39
40 int foo::value() const { return impl()->value_; }
41
42
Here, the destructor needs to be declared virtual foo so that
std::auto_ptr<foo> calls foo_impl's destructor. If you use
boost::shared_ptr<foo> instead, even that doesn't need to be
virtual, because shared_ptr remembers how to call the correct
destructor. (This doesn't improve performance or memory use, because
shared_ptr is larger and slower than auto_ptr, but if you need to use
shared_ptr anyway you may as well eliminate the virtual destructor.) -- BenHutchings参考阅读:
Effective C++
http://c2.com/cgi/wiki?PimplIdiom
http://en.wikipedia.org/wiki/Opaque_pointer