我们有时会以减少依赖或效率的名义倾向走捷径,但某些时刻这可能不是个好主意。下面有个出色的惯用语法同时兼顾了安全和实现目标。
问题
调用标准malloc() 和 new()代价昂贵。在下面的代码中,class Y中有个类型为X的数据成员
// file y.h #include "x.h" class Y { /*...*/ X x_; }; // file y.cpp Y::Y() {}类Y的声明需要类X的声明可见,为了避免这种情况,程序员尝试如下:
// file y.h class X; class Y { /*...*/ X* px_; }; // file y.cpp #include "y.h" Y::Y() : px_( new X ) {} Y::~Y() { delete px_; px_ = 0; } 这很好的隐藏了X,但结果是Y被广泛使用,动态分配降低了性能。做后程序员无畏的写下了下面“完美的”代码,既不需要在y.h中包含x.h也没有动态分配的消耗。// file y.h class Y { /*...*/ static const size_t sizeofx = /*some value*/; char x_[sizeofx]; }; // file y.cpp #include "x.h" Y::Y() { assert( sizeofx >= sizeof(X) ); new (&x_[0]) X; } Y::~Y() { (reinterpret_cast<X*>(&x_[0]))->~X(); }讨论
解决方法
不要这样做!底线,C++不直接支持不透明类型。程序员想要的是其他东西,即"Fast Pimpl" idiom。"Fast Pimpl"隐藏X的目的是避免把X暴露给Y的客户。通常C++消除这种实现依赖的措施是使用 pimpl idiom。即上述第一段代码。
这种情况下唯一的问题是pimpl方法的性能,因为需要在空闲存储器给X分配空间。通常对于特定类,解决方法是重载这个类的operator new,因为分配固定大小的空间比一般的分配子更有效率。
不幸的是,这就假设Y的作者也是X的作者。通常这并不是事实。实际解决之道是使用Efficient Pimpl。// file y.h class YImpl; class Y { /*...*/ YImpl* pimpl_; }; // file y.cpp #include "x.h" struct YImpl { // yes, 'struct' is allowed :-) /*...private stuff here...*/ void* operator new( size_t ) { /*...*/ } void operator delete( void* ) { /*...*/ } }; Y::Y() : pimpl_( new YImpl ) {} Y::~Y() { delete pimpl_; pimpl_ = 0; } 如何实现有效的固定大小的分配函数,就可用性方面,一个技术是使用泛形模板:template<size_t S> class FixedAllocator { public: void* Allocate( /*requested size is always S*/ ); void Deallocate( void* ); private: /*...implemented using statics?...*/ };由于私有部分很可能是static,这就有个问题,如果静态对象的dtor曾经调用过Deallocate()。更安全的做法应该使用singleton来管理请求不同大小的空闲链表。class FixedAllocator { public: static FixedAllocator* Instance(); void* Allocate( size_t ); void Deallocate( void* ); private: /*...singleton implementation, typically with easier-to-manage statics than the templated alternative above...*/ };把调用封装进基类:struct FastPimpl { void* operator new( size_t s ) { return FixedAllocator::Instance()->Allocate(s); } void operator delete( void* p ) { FixedAllocator::Instance()->Deallocate(p); } };现在你就可以很容易的写出很多你想要的Fast Pimpls// Want this one to be a Fast Pimpl? // Easy, then just inherit... struct YImpl : FastPimpl { /*...private stuff here...*/ };