代码实现:
class NLComponent {// 抽象基类,其中内含至少一个纯虚函数
public:
...
};
class TextBlock: public NLComponent{// 没有内含任何纯虚函数
public:
...
};
class Graphic: public NLComponent{// 没有内含任何纯虚函数
public:
...
};
class NewsLetter { // 一份实时通信是由一系列的NLComponent对象构成的
public:
NewsLetter(istream& str);// NewsLetter拥有一个istream为自变量
// 的构造函数,函数由stream读取数据以便产生必要的核心数据结构
...
private:
// readComponent 从str读取下一个NLComponent的数据,产生组件,并返回一个指针指向它
static NLComponent* readComponent(istream &str); // 要注意这里返回的是base类指针
list<NLComponent *> components;
};
NewsLetter::NewsLetter(istream &str) // 构造函数
{
while(str)
{// 将readComponent返回的指针加到Component list尾端
components.push_back(readComponent(str));
}
}
由以上代码可知:
NLComponent*
)类型存储起来,用来在以后的调用可以实现多态,这样就是 virtual constructor。class NLComponent {
public:
// 声明virtual copy constructor
virtual NLComponent * clone() const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual TextBlock * clone() const // virtual copy constructor
{ return new TextBlock(*this); } // 返回该对象的副本
...
};
class Graphic: public NLComponent {
public:
virtual Graphic * clone() const // virtual copy constructor
{ return new Graphic(*this); }
...
};
class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs);
...
private:
list<NLComponent*> components;
};
NewsLetter::NewsLetter(const NewsLetter& rhs){
//遍历rhs的list,运用每个元素的virtual copy constructor将元素复制到此对象的compnents list中.
for (list<NLComponent*>::const_iterator it =rhs.components.begin();it != rhs.components.end();++it)
//it指向rhs.compnents的目前元素,调用该元素的clone函数取得一个副本并加到本对象的compnents list的尾端
components.push_back((*it)->clone());
}
#include
using namespace std;
class NLComponent{
public:
virtual ostream& print(ostream& s) const = 0;
};
class TextBlock:public NLComponent{
public:
virtual ostream& print(ostream& s) const // 虚函数实现
{
s << "TextBlock";
return s;
}
};
class Graphic : public NLComponent{
public:
virtual ostream& print(ostream& s) const
{
s << "Graphic";
return s;
}
};
inline ostream& operator<<(ostream& s, const NLComponent& c)
{
return c.print(s);
}
int main(){
TextBlock tx;
Graphic gc;
cout << tx << endl;
cout << gc << endl;
return 0;
}
// 这里的print函数是虚函数
// 再重载一个operator<<来调用print
// 方案一的第一版:【友元函数版本】
// 将构造函数设为private,
// 然后声明一个友元函数调用它,
// 并生成一个static对象
class PrintJob;
class Printer {
public:
void submitJob(const PrintJob& job);
void reset();
void performSelfTest();
...
friend Printer& thePrinter();
private:
Printer();
Printer(const Printer& rhs);// 不允许拷贝
...
};
Printer& thePrinter(){// 要使用Printer class的时候,就调用thePrinter
static Printer p;// static保证了只产生一个Printer对象
return p;
}
// 方案一的第二版:【成员函数版本】
// 在这版中,static的Printer成为了类内的static成员
// 第一版的“函数中的static对象”保证了在第一次调用函数时才被初始化
class Printer {
public:
static Printer& thePrinter();
...
private:
Printer();
Printer(const Printer& rhs);
...
};
Printer& Printer::thePrinter(){
static Printer p;
return p;
}
// 注意带有局部 static 函数不要转为 inline
class Printer {
public:
class TooManyObjects{}; // 异常
Printer(); // 构造函数是 public 的
~Printer();
...
private:
static size_t numObjects;
Printer(const Printer& rhs);//由于只允许产生一个对象,所以不允许拷贝
};
size_t Printer::numObjects = 0;
Printer::Printer(){
if (numObjects >= 1) {
throw TooManyObjects();
}
// proceed with normal construction here;
++numObjects;
}
Printer::~Printer(){
// perform normal destruction here;
--numObjects;
}
// 涉及继承:
class ColorPrinter: public Printer {
...
};
Printer p;
ColorPrinter cp; // 抛出异常
// 涉及内含:
class CPFMachine {
private:
Printer p;
FaxMachine f;
CopyMachine c;
...
};
CPFMachine m1;
CPFMachine m2; // 抛出异常
class FSA {
public:
static FSA* makeFSA(); // 伪构造函数
static FSA* makeFSA(const FSA& rhs); // 伪拷贝构造函数
private: // 将真正的构造函数设为 private
FSA();
FSA(const FSA& rhs);
}
FSA* FSA::makeFSA()
{ return new FSA();}
FSA* FSA::makeFSA(const FSA& rhs)
{ return new FSA(rhs);}
// 使用如下:为了保证没有资源泄露,可以使用 auto_ptr
auto_ptr<FSA> pfsal(FSA::makeFSA());
// 定义如下:
class Printer {
public:
class TooManyObjects{};
static Printer * makePrinter(); // 伪构造函数
static Printer * makePrinter(const Printer& rhs); // 伪拷贝构造函数
...
private:
static size_t numObjects;
static const size_t maxObjects = 10; // 对象的数目限制,也可以使用枚举
Printer();
Printer(const Printer& rhs);
};
// 实现如下:
size_t Printer::numObjects = 0;
const size_t Printer::maxObjects;
Printer::Printer(){
if (numObjects >= maxObjects) {
throw TooManyObjects();
}
...
}
Printer::Printer(const Printer& rhs){
if (numObjects >= maxObjects) {
throw TooManyObjects();
}
...
}
Printer * Printer::makePrinter(){ // 伪构造函数
return new Printer;
}
Printer * Printer::makePrinter(const Printer& rhs){ // 伪拷贝构造函数
return new Printer(rhs);
}
template<class BeingCounted>
class Counted {
public:
class TooManyObjects{};
static int objectCount() { return numObjects; }
protected:
Counted();
Counted(const Counted& rhs);
~Counted() { --numObjects; }
private:
static int numObjects;
static const size_t maxObjects;
void init();
};
template<class BeingCounted>
Counted<BeingCounted>::Counted(){
init();
}
template<class BeingCounted>
Counted<BeingCounted>::Counted(const Counted<BeingCounted>&){
init();
}
template<class BeingCounted>
void Counted<BeingCounted>::init(){
if (numObjects >= maxObjects) throw TooManyObjects()
++numObjects;
}
class Printer: private Counted<Printer> {
// Printer的其他内容都和上一部分内容一样
// 即保持将真正构造函数 private 和提供伪构造函数接口
public:
// ...
using Counted<Printer>::objectCount; // 函数,返回当前计数
using Counted<Printer>::TooManyObjects; // 异常
// ...
private:
// ...
};
numObjects
可以在头文件中初始化为 0;maxObjects
只能由用户定义并初始化,如果用户忘记定义,链接时将会报错并提醒。【因为 maxObjects
不好由类设计者提供初始化】class UPNumber {
public:
// 如果产生一个非堆对象,就抛出异常
class HeapConstraintViolation {};
static void * operator new(size_t size);
UPNumber();
...
private:
static bool onTheHeap;// 标志对象是否被构造于堆上
...
};
// 类外部定义静态成员
bool UPNumber::onTheHeap = false;
void *UPNumber::operator new(size_t size)
{
onTheHeap = true;// 调用一次new,就会更新标志位
return ::operator new(size);
}
UPNumber::UPNumber()
{
if (!onTheHeap) {
throw HeapConstraintViolation();
}
// proceed with normal construction here;
onTheHeap = false;// 清除flag
}
onTheHeap
就会在 operator new 中被设为 true
,构造函数被正常调用;如果对象不是产生于堆中,onTheHeap
就为 false
,调用构造函数时就会抛出异常。onTheHeap
置为 true
,以后将不再调用 operator new[];所以数组中的元素在第二次调用构造函数时候就抛出了异常。UPNumber *pn = new UPNumber(*new UPNumber);
// 暂且不理睬资源泄露的问题
// 这里可能产生的问题是:
// 通常情况下,我们认为:
// 先完成第一个对象的new操作和调用构造函数操作,
// 紧接着完成第二个对象的new操作和调用构造函数操作。
// 但编译器有可能会产生下面的函数调用顺序:
// 1.为第一个对象调用operator new
// 2.为第二个对象调用operator new
// 3.为第一个对象调用constructor
// 4.为第二个对象调用constructor
// 这时候策略一就会失效。
bool onHeap(const void *address){
char onTheStack; // 局部栈变量
return address < &onTheStack; // 比较 address 和 局部栈变量 的地址
}
struct Asset{ // 包含关系
int a;
UPNumber value;
...
}
Asset* a(new Asset);
UPNumber* ptr=&(a->value);
// 尽管ptr指向的是heap内存,但对ptr实行delete会出错
// a是通过new取得,但它的成员value并不是通过new取得
// 对一个指针使用delete是否安全,并不取决于对象是否位于heap中,
// 本质上取决于它是否位于申请的一段heap内存的开始处
void *operator new(size_t size)
{
void *p = getMemory(size); // 调用函数分配内存并处理内存不足的情况
// add p to the collection of allocated addresses;// 加入到表中
return p;
}
void operator delete(void *ptr)
{
releaseMemory(ptr); // 归还内存
// remove ptr from the collection of allocated addresses;// 从表中移出来
}
bool isSafeToDelete(const void *address)// 判断地址是否在表中
{
return whether address is in collection of allocated addresses;
}
class Base1{
public:
virtual ~Base(){}
...
private:
int a;
}
class Base2{
public:
virtual ~Base2(){}
...
private:
int b;
}
class Derived:public Base1,public Base2{}
Base2* ptr = new Derived;
// ptr所指地址不在所维护的表中,因此isSafeToDelete返回false
// 但对ptr使用delete却是安全的,因为Base2的析构函数为虚
class HeapTracked {
public:
class MissingAddress{}; // 异常类
virtual ~HeapTracked() = 0;
static void *operator new(size_t size);
static void operator delete(void *ptr);
bool isOnHeap() const;
private:
typedef const void* RawAddress;
static list<RawAddress> addresses;//维护heap地址的表
}list<RawAddress> HeapTracked::addresses;
// 析构函数设为纯虚函数以使得该类成为抽象类,但必须有定义.
HeapTracked::~HeapTracked() {}
void * HeapTracked::operator new(size_t size)
{
void *memPtr = ::operator new(size);
addresses.push_front(memPtr); // 在表中插入新地址
return memPtr;
}
void HeapTracked::operator delete(void *ptr)
{
//查找是否在表中
list<RawAddress>::iterator it =find(addresses.begin(), addresses.end(), ptr);
if (it != addresses.end()) {
addresses.erase(it);
::operator delete(ptr);
}
else {
throw MissingAddress();
}
}
bool HeapTracked::isOnHeap() const
{
// 得到一个指针,指向*this占据的内存空间的起始处,
const void *rawAddress = dynamic_cast<const void*>(this);
// 在表中查找
list<RawAddress>::iterator it =find(addresses.begin(), addresses.end(), rawAddress);
return it != addresses.end(); // 返回it是否被找到
}
const void *rawAddress = dynamic_cast<const void*>(this);
// 这里利用了dynamic_cast的一个特性:
// 它返回的指针指向原生指针的内存起始处,从而解决了策略3的多继承对象内存不唯一问题
template<class T>
class auto_ptr {
public:
...
auto_ptr(auto_ptr<T>& rhs);
auto_ptr<T>& operator=(auto_ptr<T>& rhs);
~auto_ptr();
...
private:
T *pointee;// 真实的指针
};
template<class T>
auto_ptr<T>::auto_ptr(auto_ptr<T>& rhs)// 拷贝构造函数
{
pointee = rhs.pointee;
rhs.pointee = 0; // 转移对象所有权
}
template<class T>
auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs)// 拷贝赋值函数
{
if (this == &rhs) // 自我赋值的情况
return *this;
delete pointee; // 要注意,这里是先删除自身,再转移
pointee = rhs.pointee; // 转移对象所有权
rhs.pointee = 0;
return *this;
}
template<class T>
SmartPtr<T>::~SmartPtr()
{
if (*this owns *pointee) {// 只有还拥有该指针,才delete,不然不用delete
delete pointee;
}
}
void printTreeNode(ostream& s, auto_ptr<TreeNode> p) // 这里的 p 是传值
{ s << *p; }
int main(){
auto_ptr<TreeNode> ptn(new TreeNode);
...
printTreeNode(cout, ptn); //通过传值方式传递auto_ptr
...
}
// 在传参的时候,ptn初始化了p,已经调用了拷贝构造,
// ptn原本所占有的资源转移到了p中。
// 当p离开printTreeNode函数的时候,就会调用析构函数去delete了p所占用资源;
// 程序员的本意只是打印,但经过函数调用的时候,
// 突然把资源给删除了,这就是为什么不要用by-value。
// pass by reference to const才是正道,修改如下:
void printTreeNode(ostream& s,const auto_ptr<TreeNode>& p)
{ s << *p; }
// 下述操作就变得允许了:
if (ptn == 0) ... // 正确
if (ptn) ... // 正确
if (!ptn) ... //正确
// 但隐式转换操作符很容易被滥用,它使得不同类型的指针可以相比较:
SmartPtr<Apple> pa;
SmartPtr<Orange> po;
// ...
if (pa == po) ...//可以通过编译
SmartPtr<TreeNode> ptn;
// ...
if (!ptn) { // 只能用operator !
// ...
}
else {
// ...
}
// 下述操作被禁止:
if (ptn == 0) ...
if (ptn) ...
template<class T>
class DBPtr {
public:
...
operator T*() { return pointee; }
...
};
// 但如前面强调的那样:隐式转换操作符很容易被滥用
// 滥用场景1:
class Tuple{...};
void processTuple(DBPtr<Tuple>& pt)
{
Tuple *rawTuplePtr = pt; // 得到内置指针
use rawTuplePtr to modify the tuple
}
// 滥用场景2:
DBPtr<Tuple> pt=new Tuple;
delete pt;//通过了,因为执行了隐式类型转换
// 但是直接delete dumb pointer必然出现问题
// 不仅会有滥用,更重要的是它还是不能做到提供和内置指针完全一样的行为
// 因为编译器禁止:连续隐式调用自定义的隐式类型转换
class TupleAccessors {
public:
TupleAccessors(const Tuple *pt); // Tuple到TupleAccessor的转换
...
};
TupleAccessors merge(const TupleAccessor& ta1,const TupleAccessors& ta2);
DBPtr<Tuple> pt1, pt2;
...
merge(pt1,pt2);//调用会出错
// 调用出错的原因也很简单:
// DBPtr到Tuple*再到TupleAccessor的转换就可以匹配merge的参数
// 但是编译器禁止这么做
class DBPtr {
public:
...
T* toPrimary() { return pointee; }
...
};
template<class T>
class SmartPtr {
public:
SmartPtr(T* realPtr = 0);
T* operator->() const;
T& operator*() const;
template<class newType> // 模板成员函数
operator SmartPtr<newType>()
{
return SmartPtr<newType>(pointee);
}
...
};
// 本模板将智能指针之间的隐式类型转换交由底层内置指针来完成
// 如果底层指针能够转换,那么包装后的智能指针也能够进行转换
const SmartPtr<CD> p=&goodCD;
SmartPtr<CD> p; // non-const 对象, non-const 指针
SmartPtr<const CD> p; // const 对象,non-const 指针
const SmartPtr<CD> p = &goodCD; // non-const 对象,const 指针
const SmartPtr<const CD> p = &goodCD; // const 对象,const 指针
SmartPtr<CD> pCD = new CD("Famous Movie Themes");
SmartPtr<const CD> pConstCD = pCD;
// 以上代码无法通过编译
template<class T> // 指向const 对象的
class SmartPtrToConst {
protected:
union {
const T* constPointee; // 提供给SmartPtrToConst 访问
T* pointee; // 提供给SmartPtr 访问
};
};
template<class T>
class SmartPtr: public SmartPtrToConst<T> {
public:
template<class constType>
operator SmartPtrToConst<constType>();
... //没有额外数据成员
};
// SmartPtrToConst使用了union
// 这样constPointee和pointee共享同一块内存
// SmartPtrToConst使用constPointee;
// SmartPtr使用pointee
reference counting 使得多个等值对象可以共享同一实值。
以下讨论以自实现的 String
为例,先给出实现代码:【基于引用计数实现的 String
类】
#include
#include
using namespace std;
class String{
public:
String(const char* initValue = nullptr);//构造函数
String(const String& rhs);//拷贝构造函数
~String();//析构函数
String& operator=(const String& rhs);//赋值运算符
const char& operator[](int index) const;//重载[]运算符,针对const Strings
char& operator[](int index);//重载[]运算符,针对non-const Strings
String operator+(const String& rhs);//重载+运算符
String& operator+=(const String& rhs);//重载+=运算符
bool operator==(const String& rhs);//重载==运算符
int getLength();//获取长度
friend istream& operator>>(istream& is, const String& str);//重载>>运算符
friend ostream& operator<<(ostream& os, const String& str);//重载<<运算符
int getRefCount();//获取引用对象的个数
private:
struct StringValue{
int refCount;//引用计数
char* data;
StringValue(const char* initValue);//构造函数
~StringValue();//析构函数
};
StringValue* value;
};
//StringValue类的构造函数
String::StringValue::StringValue(const char* initValue):refCount(1){
if (initValue == nullptr){
data = new char[1];
data[0] = '\0';
}
else{
data = new char[strlen(initValue) + 1];
strcpy(data, initValue);
}
}
//StringValue类的析构函数
String::StringValue::~StringValue(){
delete[] data;
data = nullptr;
}
//String类的构造函数
String::String(const char* initValue):value(new StringValue(initValue))
{}
//String类的拷贝构造函数
String::String(const String& rhs) : value(rhs.value){
++value->refCount;//引用计数加1!!!
}
//String类的析构函数
String::~String(){
if (--value->refCount == 0){//析构时引用计数减1,当变为0时,没有指针指向该内存,销毁
delete value;
}
}
//String类的赋值运算符
String& String::operator=(const String& rhs){
if (value == rhs.value) //自赋值
return *this;
//赋值时左操作数引用计数减1,当变为0时,没有指针指向该内存,销毁
if (--value->refCount == 0)
delete value;
//不必开辟新内存空间,只要让指针指向同一块内存,并把该内存块的引用计数加1
value = rhs.value;
++value->refCount;
return *this;
}
//重载[]运算符,针对const Strings
const char& String::operator[](int index) const{
if(index<strlen(value->data))
return value->data[index];
}
//重载[]运算符,针对non-const Strings
char& String::operator[](int index){
if (value->refCount>1)
{//如果本对象和其他String对象共享同一实值,
//就分割(复制)出另一个副本供本对象自己使用
--value->refCount;
value = new StringValue(value->data);
}
if (index<strlen(value->data))
return value->data[index];
}
//String类的重载+运算符
String String::operator+(const String& rhs){
return String(*this) += rhs;
}
//String类的重载+=运算符
String& String::operator+=(const String& rhs){
//左操作数引用计数减1,当变为0时,没有指针指向该内存,销毁
if (--value->refCount == 0)
delete value;
//右操作数为空
if (rhs.value->data == nullptr){
value = new StringValue(value->data);
return *this;
}
//左操作数为空
if (this->value->data == nullptr){
value = new StringValue(rhs.value->data);
return *this;
}
//都不空
char* pTemp = new char[strlen(this->value->data) + strlen(rhs.value->data) + 1];
strcpy(pTemp, this->value->data);
strcat(pTemp, rhs.value->data);
value=new StringValue(pTemp);
return *this;
}
//重载==运算符
bool String::operator==(const String& rhs){
return strcmp(this->value->data, rhs.value->data) == 0 ? true : false;
}
//获取长度
int String::getLength(){
return strlen(this->value->data);
}
//重载>>运算符
istream& operator>>(istream& is, const String& str){
is >> str.value->data;
return is;
}
//重载<<运算符
ostream& operator<<(ostream& os, const String& str){
os << str.value->data;
return os;
}
//获取引用对象的个数
int String::getRefCount(){
return value->refCount;
}
Reference Counting(引用计数)的实现:
String
内嵌一个结构体 StringValue
,StringValue
主要用于存储引用计数和字符串值。【使得引用计数和字符串值相关联】StringValue
只对 String
类可见,而对客户不可见,接口由 String
定义并提供给客户。String
的有参构造函数只能以初值来构造:【但这样的实现导致分开构造但拥有相同初值的 String
对象,并不共享同一个数据结构】// String的有参构造函数:
String::String(const char *initValue): value(new StringValue(initValue)){}
String s1("More Effective C++");
String s2("More Effective C++");
// 上述的代码中,s1和s2的值相同,但是却并不共享同一块内存,二十各自拥有独立内存
// 解决方法在书中并没有讨论,但提供了思路:
// 令String统计和追踪现有的StringValue对象,只有面对真正独一无二的字符串时才产生新的StringValue对象
String
的拷贝构造函数就利用了 StringValue
的引用计数,这样便可共享内存:【要注意的是 String
中带的成员变量只有一个 StringValue
的指针,拷贝 String
就相当于拷贝一个指针,然后把指针指向的引用计数加一即可】String::String(const String& rhs): value(rhs.value){ ++value->refCount; }
String
的析构函数负责在引用计数为零的时候撤销内存:【先判断引用计数,如果不为零,就什么也不做】String::~String()
{
if (--value->refCount == 0)
delete value;
}
String
的赋值操作符如上面总体代码所示,要注意的只是处理自我赋值的情况。Copy-on-Write(写时才复制):
const char& String::operator[](int index) const{ return value->data[index]; }
String
的修改不应影响到共享内存的其他 String
对象,所以要先为当前 String
分配独立内存,并将原值进行拷贝】char& String::operator[](int index)
{
if (value->refCount > 1) {// 其实就是缓式评估的一个应用
--value->refCount;
value =new StringValue(value->data);
}
return value->data[index];
}
Pointers,References 以及 Copy-on-Write。
String
实现选择忽略此问题,或者是在文档中提示不要这样做】String s1 = "Hello";
char *p = &s1[1];// 此时p握有对'e'的控制权
String s2 = s1;
// 接下来更改p的值,s2和s1都会受影响:
*p = 'x';
// s1和s2内部无法获知p的操作,因为它们之间并没有联系
StringValue
对象加多一个标志变量,在最初标志设置为 true
(表明可以被共享);一旦 non-const operator[] 被调用就将该变量设为 false
,并可能永远不许再更改。class String {
private:
struct StringValue {
int refCount;
bool shareable; // 新增此行
char *data;
StringValue(const char *initValue);
~StringValue();
};
...
};
String::StringValue::StringValue(const char *initValue): refCount(1),
shareable(true) // 新增此行
{
data = new char[strlen(initValue) + 1];
strcpy(data, initValue);
}
String::StringValue::~StringValue()
{
delete [] data;
}
// 其他返回引用的member function(对于String只有operator[])都涉及到对flag的修改
// 其他可能需要共享内存的member function都涉及到对flag的检测
String::String(const String& rhs)
{
if (rhs.value->shareable) {// 在使用共享内存之前,先测试内存是否允许被共享
value = rhs.value;
++value->refCount;
}
else {
value = new StringValue(rhs.value->data);
}
}
StringValue
对象的个数。一个 Reference-Counting(引用计数)基类。
// 定义如下:
class RCObject {
public:
RCObject();
RCObject(const RCObject& rhs);
RCObject& operator=(const RCObject& rhs);
virtual ~RCObject() = 0;
void addReference();
void removeReference();
void markUnshareable();
bool isShareable() const;
bool isShared() const;
private:
int refCount;
bool shareable;
};
// 具体实现如下:
RCObject::RCObject(): refCount(0), shareable(true) {}
// 拷贝构造函数:
// 要注意,参数没有名字,也就是说参数没有作用,即拷贝构造函数只是形式上的
// 至于refCount初始化为0,只是因为要求对象创建者自行将refCount设为1
RCObject::RCObject(const RCObject&):refCount(0),shareable(true) {}
// 赋值操作符:
// 要注意的地方也是一样的,参数没有名字
// 它什么也不做,仅仅只是返回*this
// 因为一个RCObject本身就不应该调用赋值操作符,例如StringValue本身就不会调用赋值
// 即使写出 sv1 = sv2; 这样的语句(其中sv1和sv2是StringValue),指向sv1和sv2的对象数目实际上并未改变
RCObject& RCObject::operator=(const RCObject&)
{ return *this; }
RCObject::~RCObject() {} // virtual dtors must always
void RCObject::addReference() { ++refCount; }
// removeReference
// 它还承担了析构函数的作用:
// 在refCount=1的时候delete销毁对象,从这里可以看出RCObject必须被产生于heap中
void RCObject::removeReference()
{ if (--refCount == 0) delete this; }
void RCObject::markUnshareable()
{ shareable = false; }
bool RCObject::isShareable() const
{ return shareable; }
bool RCObject::isShared() const
{ return refCount > 1; }
class String {
private:
// StringValue继承了RCObject的接口并供String使用,所以StringValue也必须构造在heap中
struct StringValue: public RCObject {// 直接继承RCObject
char *data;
StringValue(const char *initValue);
~StringValue();
};
...
};
String::StringValue::StringValue(const char *initValue)
{
data = new char[strlen(initValue) + 1];
strcpy(data, initValue);
}
String::StringValue::~StringValue()
{
delete [] data;
}
自动操作 Reference Count(引用计数)。
RCObject
提供了一定程度的代码复用功能,但还远远不够。
String
类仍然需要手动调用 RCObject
的成员函数来对引用计数进行更改。String
和 StringValue
之间加多一层:【加一层智能指针类对引用计数进行管理】// 管理引用计数的智能指针类
template<class T>
class RCPtr {
public:
RCPtr(T* realPtr = 0);
RCPtr(const RCPtr& rhs);
~RCPtr();
RCPtr& operator=(const RCPtr& rhs);
T* operator->() const; // see Item 28
T& operator*() const; // see Item 28
private:
T *pointee;
void init(); // 将构造函数中的重复操作提取成一个函数
};
// 构造函数如下:
template<class T>
RCPtr<T>::RCPtr(T* realPtr): pointee(realPtr)
{
init();
}
template<class T>
RCPtr<T>::RCPtr(const RCPtr& rhs): pointee(rhs.pointee)
{
init();
}
template<class T>
void RCPtr<T>::init()
{
if (pointee == 0) {
return;
}
if (pointee->isShareable() == false) {
pointee = new T(*pointee); // 这里调用了T的拷贝构造函数
}
pointee->addReference();//引用计数的更改负担转移到这里
}
/*
// 为了防止编译器为StringValue合成的拷贝构造函数执行浅复制
// 需要为StringValue定义执行深度复制的拷贝构造函数【前面代码中已有体现】
String::StringValue::StringValue(const StringValue& rhs)// 注意这里讨论的是StringValue的拷贝构造
{
data = new char[strlen(rhs.data) + 1];
strcpy(data, rhs.data);
}
*/
// 由于多态性的存在,尽管pointee是T*类型,但它实际可能指向T类型的派生类
// 在此情况下new调用的却是T的拷贝构造函数
// 要防止这种现象,可以使用virtual copy constructor(见条款25),这里不再讨论
// RCPrt的其余实现如下:
template<class T>
RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)
{
if (pointee != rhs.pointee) {
if (pointee) {
pointee->removeReference();
}
pointee = rhs.pointee;
init();
}
return *this;
}
template<class T>
RCPtr<T>::~RCPtr()
{
if (pointee)pointee->removeReference();
}
template<class T>
T* RCPtr<T>::operator->() const { return pointee; }
template<class T>
T& RCPtr<T>::operator*() const { return *pointee; }
将前面所有的努力进行汇总。
各个类的实现如下:
//用于产生智能指针的类模板,T必须继承自RCObject
template<class T>
class RCPtr {
public:
RCPtr(T* realPtr = 0);
RCPtr(const RCPtr& rhs);
~RCPtr();
RCPtr& operator=(const RCPtr& rhs);
T* operator->() const;
T& operator*() const;
private:
T *pointee;
void init();
};
//抽象基类用于引用计数
class RCObject {
void addReference();
void removeReference();
void markUnshareable();
bool isShareable() const;
bool isShared() const;
protected:
RCObject();
RCObject(const RCObject& rhs);
RCObject& operator=(const RCObject& rhs);
virtual ~RCObject() = 0;
private:
int refCount;
bool shareable;
};
//应用性class
class String {
public:
String(const char *value = "");
const char& operator[](int index) const;
char& operator[](int index);
private:
//勇于表现字符串值
struct StringValue: public RCObject {
char *data;
StringValue(const char *initValue);
StringValue(const StringValue& rhs);
//
void init(const char *initValue);
~StringValue();
};
RCPtr<StringValue> value;
};
// 具体实现如下:
// RCObject的实现
RCObject::RCObject(): refCount(0), shareable(true) {}
RCObject::RCObject(const RCObject&): refCount(0), shareable(true) {}
RCObject& RCObject::operator=(const RCObject&)
{ return *this; }
RCObject::~RCObject() {}
void RCObject::addReference() { ++refCount; }
void RCObject::removeReference()
{ if (--refCount == 0) delete this; }
void RCObject::markUnshareable()
{ shareable = false; }
bool RCObject::isShareable() const
{ return shareable; }
bool RCObject::isShared() const
{ return refCount > 1; }
// RCPtr的实现
template<class T>
void RCPtr<T>::init()
{
if (pointee == 0) return;
if (pointee->isShareable() == false) {
pointee = new T(*pointee);
}
pointee->addReference();
}
template<class T>
RCPtr<T>::RCPtr(T* realPtr): pointee(realPtr)
{ init(); }
template<class T>
RCPtr<T>::RCPtr(const RCPtr& rhs): pointee(rhs.pointee)
{ init(); }
template<class T>
RCPtr<T>::~RCPtr()
{ if (pointee)pointee->removeReference(); }
template<class T>
RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)
{
if (pointee != rhs.pointee) {
if (pointee) pointee->removeReference();
pointee = rhs.pointee;
init();
}
return *this;
}
template<class T>
T* RCPtr<T>::operator->() const { return pointee; }
template<class T>
T& RCPtr<T>::operator*() const { return *pointee; }
// StringValue的实现
void String::StringValue::init(const char *initValue)
{
data = new char[strlen(initValue) + 1];
strcpy(data, initValue);
}
String::StringValue::StringValue(const char *initValue)
{ init(initValue); }
String::StringValue::StringValue(const StringValue& rhs)
{ init(rhs.data); }
String::StringValue::~StringValue()
{ delete [] data; }
// String的实现
String::String(const char *initValue): value(new StringValue(initValue)) {}
const char& String::operator[](int index) const
{ return value->data[index]; }
char& String::operator[](int index)
{
//String类唯一需要接触底层成员的负担
if (value->isShared()) { value = new StringValue(value->data); }
value->markUnshareable(); return value->data[index];
}
// 可以看出String的实现异常简单,因为所有的引用计数任务全部交由其他可移植性类实现
将 Reference Counting 加到既有的 Classes 身上。
有了以上设计,就可以实现任何需要引用计数功能的类。【只要继承 RCObject
,并作为已有的 RCPtr
模板的类型参数即可】
Widget
的类,我们无法修改它;但只要采取之前所用的中间加一层的方法,这种目标仍可以达成。首先,考虑第一种情况,即 Widget
是我们能修改的;使用方法就是直接令 Widget
继承 RCObject
,相当于充当了 StringValue
的角色,具体如下图所示:
接着,考虑第二种情况,即 Widget
来自程序库,我们无法修改;采用中间加一层的方法,增加一个新的 CountHolder
类(继承自 RCObject
并持有 Widget
指针);把 RCPtr
类模板用具有相同功能但内部定义了 CountHolder
的 RCIPtr
取代(PCIPtr
的 I 是 indirect 间接的意思),具体如下图所示:
RCIPtr
和 CountHolder
的实现如下:template<class T>
class RCIPtr {
public:
RCIPtr(T* realPtr = 0);
RCIPtr(const RCIPtr& rhs);
~RCIPtr();
RCIPtr& operator=(const RCIPtr& rhs);
const T* operator->() const;
T* operator->();
const T& operator*() const;
T& operator*();
private:
// CountHolder只对RCIPter可见,因此设为private
struct CountHolder: public RCObject {
~CountHolder() { delete pointee; }
T *pointee;
};
CountHolder *counter;
void init();
void makeCopy();
};
template<class T>
void RCIPtr<T>::init()
{
if (counter->isShareable() == false) {
T *oldValue = counter->pointee;
counter = new CountHolder;
counter->pointee = new T(*oldValue);
}
counter->addReference();
}
template<class T>
RCIPtr<T>::RCIPtr(T* realPtr): counter(new CountHolder)
{
counter->pointee = realPtr;
init();
}
template<class T>
RCIPtr<T>::RCIPtr(const RCIPtr& rhs): counter(rhs.counter)
{ init(); }
template<class T>
RCIPtr<T>::~RCIPtr()
{ counter->removeReference(); }
template<class T>
RCIPtr<T>& RCIPtr<T>::operator=(const RCIPtr& rhs)
{
if (counter != rhs.counter) {
counter->removeReference();
counter = rhs.counter;
init();
}
return *this;
}
template<class T>
void RCIPtr<T>::makeCopy()
{
if (counter->isShared()) {
T *oldValue = counter->pointee;
counter->removeReference();
counter = new CountHolder;
counter->pointee = new T(*oldValue);
counter->addReference();
}
}
template<class T>
const T* RCIPtr<T>::operator->() const // const版本,只读
{ return counter->pointee; }
template<class T>
T* RCIPtr<T>::operator->() // non-const版本,即copy-on-write的实现
{
makeCopy();
return counter->pointee;
}
template<class T>
const T& RCIPtr<T>::operator*() const // const版本,只读
{ return *(counter->pointee); }
template<class T>
T& RCIPtr<T>::operator*() // non-const版本,即copy-on-write的实现
{
makeCopy();
return *(counter->pointee);
}
RCWidget
只要通过底层的 RCIPtr
调用对应的 Widget
函数即可:// Widget的接口如下:
class Widget {
public:
Widget(int size);
Widget(const Widget& rhs);
~Widget();
Widget& operator=(const Widget& rhs);
void doThis();
int showThat() const;
// ...
};
// RCWidget定义如下:
class RCWidget {
public:
RCWidget(int size): value(new Widget(size)) {}
void doThis() { value->doThis(); }
int showThat() const { return value->showThat(); }
private:
RCIPtr<Widget> value;
};
最后的总结:
template<class T>
class Array2D {
public:
Array2D(int dim1, int dim2);
...
};
template<class T>
class Array2D {
public:
//代理类
class Array1D {
public:
T& operator[](int index);
const T& operator[](int index) const;
...
};
Array1D operator[](int index);
const Array1D operator[](int index) const;
...
};
// 如此一来,以下操作便可实现:
Array2D<float> data(10, 20);
...
cout << data[3][6];
// data[3][6]实际上进行了两次函数调用:
// 第一次调用Array2D的operator[],返回Array1D对象
// 第二次调用Array1D的operator[],返回指定元素
class String {
public:
//代理类用于区分operator[]的读写操作
class CharProxy { // proxies for string chars
public:
CharProxy(String& str, int index); // creation
CharProxy& operator=(const CharProxy& rhs); // 左值运用,意味着等号左边是CharProxy
// 如果左边是CharProxy,说明是作为左值存在的,此时便调用上面这个版本
CharProxy& operator=(char c);
operator char() const; // 右值运用,就直接转成char,然后读出去了
private:
String& theString; //用于操作String,并在适当时机开辟新内存并复制
int charIndex;
};
const CharProxy operator[](int index) const; // for const Strings
CharProxy operator[](int index); // for non-const Strings
...
friend class CharProxy;
private:
RCPtr<StringValue> value;//见条款29
};
// 对String调用operator[]将返回CharProxy对象
// CharProxy通过重载oeprator char模拟char类型的行为
// 当对CharProxy使用operator=时,便可得知是对CharProxy进行写操作
// 具体实现如下:
String::CharProxy::CharProxy(String& str, int index): theString(str), charIndex(index) {}
String::CharProxy::operator char() const
{
return theString.value->data[charIndex];
}
String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs)// 左值,就是写操作,需要开辟内存并构造
{
if (theString.value->isShared()) {
theString.value = new StringValue(theString.value->data);
}
theString.value->data[charIndex] = rhs.theString.value->data[rhs.charIndex];
return *this;
}
String::CharProxy& String::CharProxy::operator=(char c)
{
if (theString.value->isShared()) {
theString.value = new StringValue(theString.value->data);
}
theString.value->data[charIndex] = c;
return *this;
}
// 以上函数的代码部分有重复,可考虑将重复部分提取成一个函数
// String的operator[]相当简单
const String::CharProxy String::operator[](int index) const
{
return CharProxy(const_cast<String&>(*this), index);
}
String::CharProxy String::operator[](int index)// non-const版本,直接返回CharProxy对象
{
return CharProxy(*this, index);
}
StringValue
类添加可共享标志来表示对象是否可被共享以防止外部指针的篡改,其中涉及到对 operator[] 返回值进行取址操作。CharProxy
也需要对 operator& 进行重载:// 定义如下:
class String {
public:
class CharProxy {
public:
...
char * operator&();
const char * operator&() const;
...
};
...
};
// 具体实现如下:
// const版本operator&实现比较直观:
const char * String::CharProxy::operator&() const
{
return &(theString.value->data[charIndex]);
}
// non-const版本的operator&要更复杂些:
char * String::CharProxy::operator&()
{
//如果正在使用共享内存,就开辟新内存并复制数据结构
if (theString.value->isShared()) {
theString.value = new StringValue(theString.value->data);
}
//由于有外部指针指向它,因此有被篡改风险,禁止使用共享内存
theString.value->markUnshareable();
return &(theString.value->data[charIndex]);
}
template<class T>
class Array{
public:
class Proxy{...};// 假设根据这个代理类重载了operator[]
}
// 下述代码仍然错误:
Array[5] += 1;
Array[5] ++;
// Array[]产生了一个代理类Proxy,但这个代理类无法作为operator +=的左手端
// 因而只能一一重载operator++、operator+=
// 假设有个Rational类,里面有成员函数A、成员函数B
// 我们再使用前面问题二中的Array模板
Array<Rational> array;
cout << array[1].A();// 错误!
int tmp = array[2].B();// 错误!
// 错误原因很简单:
// array[]返回的是代理类,不是真正的Rational,所以无法调用成员函数
// 解决方法也很麻烦:需要将适用于真实对象的每一个函数加以重载
swap
函数用于对象两个 char
的内容:void swap(char& a, char& b);// 注意传参是引用,但代理类只能转成char
// 即使能转成char&,也有问题;因为临时对象转成non-const reference参数也是不允许的
// 无法将CharProxy做参数传递给swap
// 因为swap的参数是char&
// 尽管CharProxy可以转换到char,但由于抓换后的char是临时对象,仍然无法绑定到char&
class GameObject { ... };// 抽象基类,游戏物体
class SpaceShip: public GameObject { ... };// 宇宙飞船
class SpaceStation: public GameObject { ... };// 空间站
class Asteroid: public GameObject { ... };// 小行星
// 底下的三个对象相撞有不同的规则,处理碰撞的函数声明如下:
void checkForCollision(GameObject& object1,GameObject& object2);
checkForCollision
函数的参数可知,要处理两个物体的碰撞,必须知道这两个引用的动态类型。
class CollisionWithUnknownObject {
public:
//处理不明撞击物时所抛出的异常
CollisionWithUnknownObject(GameObject& whatWeHit);
...
};
void SpaceShip::collide(GameObject& otherObject)// 一个函数处理多个类别
{
const type_info& objectType = typeid(otherObject);
if (objectType == typeid(SpaceShip)) {
SpaceShip& ss = static_cast<SpaceShip&>(otherObject);
process a SpaceShip-SpaceShip collision;
}
else if (objectType == typeid(SpaceStation)) {
SpaceStation& ss =static_cast<SpaceStation&>(otherObject);
process a SpaceShip-SpaceStation collision;
}
else if (objectType == typeid(Asteroid)) {
Asteroid& a = static_cast<Asteroid&>(otherObject);
process a SpaceShip-Asteroid collision;
}
//处理不明撞击物
else {
throw CollisionWithUnknownObject(otherObject);
}
}
collide
函数可能都需要添加处理新型碰撞的代码;而且使用 RTTI 实现 double-dispatching,也将根据参数动态类型采取不同行为的负担加在了程序员身上。collide
的重载,将 double dispatch 以两个 single dispatch 实现:【类似于递归思想】class SpaceShip; // 前置声明
class SpaceStation;
class Asteroid;
class GameObject {
public:
virtual void collide(GameObject& otherObject) = 0;
virtual void collide(SpaceShip& otherObject) = 0;
virtual void collide(SpaceStation& otherObject) = 0;
virtual void collide(Asteroid& otherobject) = 0;
...
};
class SpaceShip: public GameObject {
public:
virtual void collide(GameObject& otherObject);
virtual void collide(SpaceShip& otherObject);
virtual void collide(SpaceStation& otherObject);
virtual void collide(Asteroid& otherobject);
...
};
// 关于GameObject&的实现如下:
void SpaceShip::collide(GameObject& otherObject)
{
otherObject.collide(*this);// 在这里会交换指针
// 在该函数内部,*this实际上已经对应该函数的动态类型
}
// 其他实现如下:
void SpaceShip::collide(SpaceShip& otherObject)
{
process a SpaceShip-SpaceShip collision;// 实际碰撞操作
}
void SpaceShip::collide(SpaceStation& otherObject)
{
process a SpaceShip-SpaceStation collision;
}
void SpaceShip::collide(Asteroid& otherObject)
{
process a SpaceShip-Asteroid collision;
}
SpaceShip
的定义如下:class GameObject {
public:
virtual void collide(GameObject& otherObject) = 0;// 注意,不再重载collide函数,只有一个
...
};
class SpaceShip: public GameObject {
public:
virtual void collide(GameObject& otherObject);// 注意,这里collide函数的参数仍然是GameObject&
virtual void hitSpaceShip(SpaceShip& otherObject);// 注意,这三行函数画线的地方后面会更改
virtual void hitSpaceStation(SpaceStation& otherObject);
virtual void hitAsteroid(Asteroid& otherobject);
...
};
void SpaceShip::hitSpaceShip(SpaceShip& otherObject)// 这三个函数后面会给出具体实现
{
process a SpaceShip-SpaceShip collision;
}
void SpaceShip::hitSpaceStation(SpaceStation& otherObject)
{
process a SpaceShip-SpaceStation collision;
}
void SpaceShip::hitAsteroid(Asteroid& otherObject)
{
process a SpaceShip-Asteroid collision;
}
// 在collide中,我们需要一种方法,将参数otherObject的动态类型映射到某个member function指针,
// 指针就能指向适当的碰撞处理函数。
// 这里实现了一个lookup函数,交给lookup一个GameObject,它会返回一个指针,指向必须调用的member function
class SpaceShip: public GameObject {
private:
typedef void (SpaceShip::*HitFunctionPtr)(GameObject&);
//碰撞处理函数指针
static HitFunctionPtr lookup(const GameObject& whatWeHit);// 声明
...
};
// collide对lookup的使用如下:
void SpaceShip::collide(GameObject& otherObject)
{
HitFunctionPtr hfp =lookup(otherObject);
if (hfp) {
(this->*hfp)(otherObject); // call it
}
//处理未知碰撞
else {
throw CollisionWithUnknownObject(otherObject);
}
}
// 虚函数表和lookup的实现如下:
// 虚函数表可以用STL内的map容器实现
// 它还要保证在第一次调用lookup函数时,就已被初始化并在程序结束后被释放
// 比较好的选择是把它声明为lookup内的static对象
class SpaceShip: public GameObject {
private:
typedef void (SpaceShip::*HitFunctionPtr)(GameObject&);
typedef map<string, HitFunctionPtr> HitMap;// map的first是对象名,即是SpaceShip还是SpaceStation
// map的second是函数指针
...
};
SpaceShip::HitFunctionPtr SpaceShip::lookup(const GameObject& whatWeHit)
{
static HitMap collisionMap;
//查找
HitMap::iterator mapEntry=collisionMap.find(typeid(whatWeHit).name());
//未知碰撞
if (mapEntry == collisionMap.end()) // 没找到,返回0
return 0;
return (*mapEntry).second;// 找到,返回函数指针
}
collisionMap
】
SpaceShip::HitFunctionPtr SpaceShip::lookup(const GameObject& whatWeHit)
{
static HitMap collisionMap;// 应该在初始化的时候直接获得这个map,所以要另写一个初始化HitMap的函数
collisionMap["SpaceShip"] = &hitSpaceShip;
collisionMap["SpaceStation"] = &hitSpaceStation;
collisionMap["Asteroid"] = &hitAsteroid;
...
}
// 会造成每次调用lookup时
// 都将hitSpaceShip,hitSpaceStation,hitAsteriod插入collisionMap内
class SpaceShip: public GameObject {
private:
static HitMap initializeCollisionMap();
...
};
SpaceShip::HitFunctionPtr SpaceShip::lookup(const GameObject& whatWeHit)
{
// 区分下面两行的写法,注释掉的代码要付出构造和析构临时HitMap对象的成本
// static HitMap collisionMap = initializeCollisionMap();
static auto_ptr<HitMap> collisionMap(initializeCollisionMap());
...
}
// initializeCollisionMap的实现如下:
SpaceShip::HitMap * SpaceShip::initializeCollisionMap()
{
HitMap *phm = new HitMap;
//正是为了以下操作,之前collide才放弃重载以使得hitSpaceShip,hitSpaceStation,hitAsteriod可以有相同参数
(*phm)["SpaceShip"] = &hitSpaceShip;
(*phm)["SpaceStation"] = &hitSpaceStation;
(*phm)["Asteroid"] = &hitAsteroid;
return phm;
}
initializeCollisionMap
无法通过编译,原因是:
HitMap
被声明为用来存放成员函数指针,这些指针都需要有相同的类型的参数。initializeCollisionMap
中每个语句下面用 reinterpret_cast。虽然这种做法通过了编译,但是通过欺骗编译器的做法达成的效果;在菱形继承的场景下,这种欺骗行为可能导致编译器传递了错误的地址。hitSpaceShip
、hitSpaceStation
和 hitAsteroid
)的参数进行更改:void SpaceShip::hitSpaceShip(GameObject& spaceShip)
{
SpaceShip& otherShip=dynamic_cast<SpaceShip&>(spaceShip);// 内部强制转换
process a SpaceShip-SpaceShip collision;
}
void SpaceShip::hitSpaceStation(GameObject& spaceStation)
{
SpaceStation& station=dynamic_cast<SpaceStation&>(spaceStation);
process a SpaceShip-SpaceStation collision;
}
void SpaceShip::hitAsteroid(GameObject& asteroid)
{
Asteroid& theAsteroid =dynamic_cast<Asteroid&>(asteroid);
process a SpaceShip-Asteroid collision;
}
//如前面所说,这三个函数的参数都统一成了GameObject&:
...
virtual void hitSpaceShip(GameObject& otherObject);
virtual void hitSpaceStation(GameObject& otherObject);
virtual void hitAsteroid(GameObject& otherobject);
...
#include "SpaceShip.h"
#include "SpaceStation.h"
#include "Asteroid.h"
//使用匿名命名空间使得名称只对本单元可见,作用等同于将名称声明为static
namespace {
void shipAsteroid(GameObject& spaceShip,GameObject& asteroid);
void shipStation(GameObject& spaceShip,GameObject& spaceStation);
void asteroidStation(GameObject& asteroid,GameObject& spaceStation);
...
//对称碰撞
void asteroidShip(GameObject& asteroid,GameObject& spaceShip){ shipAsteroid(spaceShip, asteroid); }
void stationShip(GameObject& spaceStation,GameObject& spaceShip){ shipStation(spaceShip, spaceStation); }
void stationAsteroid(GameObject& spaceStation,GameObject& asteroid){ asteroidStation(asteroid, spaceStation); }
...
typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
typedef map< pair<string,string>, HitFunctionPtr > HitMap;
pair<string,string> makeStringPair(const char *s1,const char *s2);
HitMap * initializeCollisionMap();
HitFunctionPtr lookup(const string& class1,
const string& class2);
} //命名空间结束
void processCollision(GameObject& object1,GameObject& object2)
{
HitFunctionPtr phf = lookup(typeid(object1).name(),typeid(object2).name());
if (phf)
phf(object1, object2);
else
throw UnknownCollision(object1, object2);
}
// 和前面的实现差别如下:
// HitFunctionPtr是一个typedef,表示指向non-member function的指针
// exception class CollisionWithUnknownObject被重命名为UnKnownCollision并使用接受两个GameObject对象的构造函数
// lookup必须接获两个GameObject名称并执行double dispatch
// 为了使得map的键含有两份信息,需要使用标准库pair类模板
// 具体实现如下:
namespace {
pair<string,string> makeStringPair(const char *s1,const char *s2){ return pair<string,string>(s1, s2); }
}
namespace {
HitMap * initializeCollisionMap()
{
HitMap *phm = new HitMap;
(*phm)[makeStringPair("SpaceShip","Asteroid")] =&shipAsteroid;
(*phm)[makeStringPair("SpaceShip", "SpaceStation")] =&shipStation;
...
return phm;
}
}
namespace {
HitFunctionPtr lookup(const string& class1,const string& class2)
{
static auto_ptr<HitMap>
collisionMap(initializeCollisionMap());
HitMap::iterator mapEntry=collisionMap->find(make_pair(class1, class2));
if (mapEntry == collisionMap->end()) return 0;
return (*mapEntry).second;
}
}
// 因为makeStringPair、initializationCollisionMap和lookup都被声明于一个匿名namespace内
// 所以它们也必须实现于相同的namespace内,使得链接器能正确将其定义和声明关联
GameObject
被添加,原有的 class 也不需要重新编译。
initializeCollisionMap
中增加对应的键值对,并在 processCollision
所在的匿名命名空间中申明一个新的碰撞处理函数即可。如果在调用碰撞处理函数时,发生了 inheritance_based 类型转换,方法就没法有效运作。
我们现在假设继承体系中增添了两个新类:【注意书中这个位置,GameObject
和 SpaceStaion
都被设置为了抽象基类】【其中 CommercialShip
和 MilitaryShip
的碰撞行为完全相同,即 SpaceStation
的子类行为是一致的】
如果 MilitrayShip
和 Asteroid
碰撞,程序员可能企图通过调用下面语句来解决:
void shipAsteroid(GameObject& spaceStation,GameObject& asteroid);
结果并不如意,会有未知碰撞异常抛出。
MilitaryShip
可以视作 SpaceStation
,但 lookup
函数查不出来;因为 lookup
是靠字符匹配的方式查找对应函数指针。在这里,如果又要实现 double dispatching,还要支持 inheritance_based 类型转换,我们只能回头去倚靠那个双虚函数调用机制。
HitMap
类单独设计,让程序员可以随时进行增删查改】
CollisionMap
类用于管理仿真虚函数表,并提供新增、删除、修改虚函数表的接口。// 定义如下:
class CollisionMap {
public:
typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
void addEntry(const string& type1,const string& type2,HitFunctionPtr collisionFunction,bool symmetric = true);//symmetric用于标记是否产生两个顺序不同的碰撞处理函数,默认为true
void removeEntry(const string& type1,const string& type2);
HitFunctionPtr lookup(const string& type1,const string& type2);
//产生唯一的CollisionMap
static CollisionMap& theCollisionMap();
private:
//构造函数声明为private从而限制CollisionMap只能产生一个
CollisionMap();
CollisionMap(const CollisionMap&);
};
// 对CollisionMap的使用如下:
void shipAsteroid(GameObject& spaceShip,GameObject& asteroid);
CollisionMap::theCollisionMap().addEntry("SpaceShip","Asteroid",&shipAsteroid);
void shipStation(GameObject& spaceShip,GameObject& spaceStation);
CollisionMap::theCollisionMap().addEntry("SpaceShip","SpaceStation",
&shipStation);
void asteroidStation(GameObject& asteroid,GameObject& spaceStation);
CollisionMap::theCollisionMap().addEntry("Asteroid","SpaceStation",&asteroidStation);
...
CollisionMap
类后,产生的问题有:
GameObject
的 subclass 的 constructor 进行检查,看看是否在对象产生之际已有适当的 map 条目加入,这需要时间的成本。RigisterCollisionFunction
类进行管理。class RegisterCollisionFunction {
public:
RegisterCollisionFunction(const string& type1,const string& type2,CollisionMap::HitFunctionPtr collisionFunction,bool symmetric = true)
{
CollisionMap::theCollisionMap().addEntry(type1, type2,collisionFunction,symmetric);
}
};
// 用户使用RigisterCollisionFunction进行“注册”:
RegisterCollisionFunction cf1(typeid(SpaceShip).name(), typeid(Asteroid).name(),&shipAsteroid);
RegisterCollisionFunction cf2(typeid(SpaceShip).name(), typeid(SpaceStation).name(),&shipStation);
RegisterCollisionFunction cf3(typeid(Asteroid), typeid(SpaceStation).name(),&asteroidStation);
...
int main(int argc, char * argv[])
{
...
}
// 这些全局对象在main函数调用之前就产生
// 因此它们的constructor所注册的条目也会在main被调用之前加入map
// 如果此后有新的derived class和相应的碰撞函数产生:
class Satellite: public GameObject { ... };
void satelliteShip(GameObject& satellite,GameObject& spaceShip);
void satelliteAsteroid(GameObject& satellite,GameObject& asteroid);
// 函数可以使用类似方法加入到map之中,而不需要改变原有代码:
RegisterCollisionFunction cf4(typeid(Satellite).name(),typeid(SpaceShip).name(),&satelliteShip);
RegisterCollisionFunction cf5(typeid(Satellite).name(),typeid(Asteroid).name(),&satelliteAsteroid);