如下采用了一些称为“降低依赖”或“效率”方面的捷径,在很多时候颇有吸引力,但它不是好主意。有一个极好的习惯可以成功同时实现这两个目标:
【问题】
标准的malloc()个new()调用的开销很大,在下面的代码,程序员最初在class Y中设计了一个类型X的成员:
//尝试#1
//文件y.h
#include "x.h"
class Y
{
/*...*/
X x_;
};
//文件y.cpp
Y::Y(){}
这个Y的声明要求X声明可见,为了避免这个问题做了第二次尝试:
//尝试#2
//文件y.h
class X;
class Y
{
/*...*/
X* px_;
};
//文件y.cpp
#include "x.h"
Y::Y():px_(new X){}
Y::~Y(){delete px_;px_ = 0;}
这很好的因此了X,但它造成了当Y被广泛使用的时候,动态内存分配开销使性能降低。
最后程序员发现“完美”解决方案:既不需要在y.h中包含x.h,也不需要动态内存分配(甚至前置声明都不需要):
//尝试#3
//文件y.h
class Y
{
/*...*/
static const size_t sizeofx = /*某个值*/;
char x_[sizeofx];
};
//文件y.cpp
#include "x.h"
Y::Y()
{
assert(sizeofx >= sizeof(X));
new(&x_[0])X;
}
Y::~Y()
{
(reinterpret_cast<X*>(&x_[0]))->~X();
}
【解答】:
不要这么做。底线:C++不直接支持不定类型,这是依赖于这一局限上的脆弱尝试。
为什么尝试#3是糟糕的:
1.对齐。不像::operator new()得到 的动态内存,这个x_的字符并不确保满足类型X的对齐要求。试图让x_工作更可靠,程序员需要使用一个"max_align"联合:
union max_align
{
short dummy0;
long dummy1;
double dummy2;
long double dummy3;
void* dummy4;
/*...*/
};
union
{
max_align m;
char x_[sizeofx];
};
这不确保完全可移植性,已经足够完美,只是极少数系统上,它不能如愿工作。
2.脆弱。Y的作者必须小心处理Y的其他普通函数。例如,Y决不能使用默认赋值操作,必须禁止或自己写一个版本。
3.维护代价。当sizeof(X)超过sizeofx,程序员必须增大sozeofx,选择一个较大的sizeofx,但换来效率降低。
4.低效率。只要sizeofx大于sizeof(X),空间就被浪费了,这个损失可以降低最小,但又造成维护代价。
更好的解决方法:
隐藏X的动机是避免Y的用户知道X。为了消除这种方法,通常使用“pimpl”方法。唯一的问题是,“pimpl”方法由于X的对象在自由空间分配内存而导致性能下降。通常采用重载的operator new固定长度的内存分配器可以比通用的内存分配器性能高的多。
不幸的是:这意味Y的作者必须也是X的作者。通常,这是不成立的:
//文件y.h
class YImpl;
class Y
{
/*...*/
YImpl* pimpl_;
};
//文件y.cpp
#include "x.h"
struct YImpl
{
/*私有成员*/
void* operator new(size_t);
void operator delete(void*);
};
Y::Y():px_(new YImpl){}
Y::~Y(){delete pimpl_;pimpl_ = 0;}
上述是所谓的“Fast Pimpl”。但是考虑下Fast Pimpl的代价。
如下是讨论其可用性:一个技巧,将它们放入一个通用尺寸内存分配器模板技术:
template<size_t S>
class FixedAllocator
{
public:
void* Allocator(/*请求的大小总是S*/);
void Deallocate(void*);
private:
/*用静态成员实现*/
};
因为它的私有细节很可能使用static实现,问题是Deallocate()是否被一个static对象的析构函数调用会有问题。这里可以使用单例模式比较替代比较安全。
template<size_t S>
class FixedAllocator
{
public:
static FixedAllocator* Instance();
void* Allocator(/*请求的大小总是S*/);
void Deallocate(void*);
private:
/*单例来实现*/
};
//辅助类
struct FastPimpl
{
void* operator new(size_t s)
{
return FixedAllocator::Instance()->Allocator(s);
}
void operator delete(void* p)
{
FixedAllocator::Instance->Deallocate(p);
}
};
//现在你很容易实现任意的fast pimpl:
struct YImpl : FastPimpl
{
/*私有成员*/
};
但是小心:这虽然很好,但是也别乱用Fast Pimpl。你得到了最佳内存分配,但是别忘了代价:维护这些独立的链表将导致空间效率下降,因为这比通常情况下更多的内存碎片。