技术
条款25:将构造函数和非成员函数虚拟化
1.虚拟构造函数是指能够根据输入给它的数据的不同而建立不同类型的对象,比如从磁盘读取对象信息。
2.被派生类重定义的虚拟函数不用必须与基类的虚拟函数具有一样的返回类型。如果函数的返回类型是一个指向基类的指针(或一个引用),那么派生类的函数可以返回一个指向基类的派生类的指针(或引用)比如virtual copy constructor,它会返回一个指针,指向其调用者的一个新副本。
3.就像构造函数不能真的成为虚拟函数一样,非成员函数也不能成为真正的虚拟函数。然而,既然一个函数能够构造出不同类型的新对象是可以理解的,那么同样也存在这样的非成员函数,可以根据参数的不同动态类型而其行为特性也不同
4.具有虚拟行为的非成员函数很简单。你编写一个虚拟函数来完成工作,然后再写一个非虚拟函数,它什么也不做只是调用这个虚拟函数。为了避免这个句法引起函数调用开销,你当然可以内联这个非虚拟函数。参考P129列子。
条款26:限制某个类所能产生的对象数量
1.阻止建立某个类的对象,最容易的方法就是把该类的构造函数声明在类的private域
2.任何能在全局域声明东西也能在命名空间namespace里声明。包括类、结构、函数、变量、对象、typedef等等。C++的一个哲学基础是,你不应该为你并不适用的东西付出任何代价,而“将打印机这类对象定义为函数内的一个static”,正是固守此哲学的一种做法。这是你应该尽可能坚持的一个哲学。
不将该静态对象放在类中原因是放在函数中时,执行函数时才建立对象,并且对象初始化时间确定的,即第一次执行该函数时
3.为什么你要把对象声明为静态的呢?通常是因为你只想要对象的一个拷贝。现在再考虑内联意味着什么?从概念上讲,它意味着编译器用函数体替代对函数的每一个调用,不过非成员函数还不只这些。它还意味着internal linkage(内部链接),如果内联可能造成程序的静态对象拷贝超过一个。所以千万不要产生内含local static对象的inline non-memberfunctions。
4.带有内部连接的函数可能在程序内被复制(也就是说程序的目标(Object)代码可能包含一个以上的内部链接函数的代码),这种复制也包括函数内的静态对象。结果如何?如果建立一个包含局部静态对象的非成员函数,你可能会使程序的静态对象的拷贝超过一个!所以不要建立包含静态数据的非成员函数
5.避免具体类继承其他的具体类,带有private constructors的classes不能改被用来当做base classes,也不能被内嵌于其他对象内。
6.要为const static members 指定初值。
7. 指定个数,方法如下:
写一个基类,用来对象技术:
Counted.h
1. template <typename T>
2. class Counted
3. {
4. public:
5. class TooManyObjects {};
6. static int objectCount() {return numObjects;}
7. protected:
8. Counted();
9. Counted(const Counted& rhs);
10.
11. ~Counted() {--numObjects;}
12. private:
13. static int numObjects;
14. static const size_t maxObjects;
15.
16. void Init();
17. };
18.
19. template <typename T>
20. Counted<T>::Counted()
21. {
22. Init();
23. }
24.
25. template <typename T>
26. Counted<T>::Counted(const Counted& rhs)
27. {
28. Init();
29. }
30. template <typename T>
31. void Counted<T>::Init()
32. {
33. if(numObjects>=maxObjects)
34. throw TooManyObjects();
35. ++numObjects;
36. };
假设我们有10台打印机,定义打印机的类如下:
1. template <typename T>
2. int Counted<T>::numObjects=0;
3.
4. class Printer : private Counted<Printer>
5. {
6. public:
7. static Printer* makePrinter();
8. static Printer* makePrinter(const Printer& rhs);
9.
10. ~Printer();
11.
12. using Counted<Printer>::objectCount; //让此函数对于Printer的用户而言成为public的
13. using Counted<Printer>::TooManyObjects;
14. private:
15. Printer();
16. Printer(const Printer& rhs);
17. };
18.
19. Printer::Printer()
20. {
21. cout<<"Printer()"<<endl;
22. }
23.
24. Printer::Printer(const Printer& rhs)
25. {
26. cout<<"Printer(const Printer& rhs)"<<endl;
27. }
28.
29. Printer* Printer::makePrinter()
30. {
31. return new Printer();
32. }
33.
34. Printer* Printer::makePrinter(const Printer& rhs)
35. {
36. return new Printer(rhs);
37. }
38.
39. const size_t Counted<Printer>::maxObjects=10; //注意将这个实现加入,否则会有链接期获得错误信息
40.
41. int main(int argc, char* argv[])
42. {
43. Printer *p1=Printer::makePrinter();
44. Printer *p2=Printer::makePrinter();
45. Printer *p3=Printer::makePrinter(*p2);
46.
47. cout<<Printer::objectCount()<<endl;
48.
49. return 0;
50. }
注意Counted类的两个static数据成员的初始化位置。通过maxObjects我们可以指定资源的上限。
当资源上限为1的时候,效果同sigleton模式。
另外,将某个类的构造函数声明为private,除了防止不能被创建对象,也能防止被派生。
8.编程点滴:
将模板类的定义和实现放在一个文件中,否则将造成引用未定义错误(血的教训);
静态数据成员需要先声明再初始化;
用常量值作初始化的有序类型的const静态数据成员是一个常量表达式(可以作为数组定义的维数);
构造函数中抛出异常,将导致静态数组成员重新初始化
条款27:要求或禁止在堆中产生对象
1.要求在堆中建立对象:非堆对象在定义它的地方被自动构造,在生存时间结束时自动被释放,所以只要禁止使用隐式的构造函数和析构函数,就可以实现这种限制。把这些调用变的不合法的一种最直接的方法是把构造函数和析构函数声明为private。这样做副作用太大。没有理由让两个函数都是private。最好让析构函数为private,让构造函数为publi c,因为一个class只能有一个destructor。你可以引进一个专用的伪析构函数,用来访问真正的析构函数。客户端调用伪析构函数释放他们建立的对象
class UPNumber
{
public:
UPNumber():value(0){cout<<"UPNumber"<<endl;}
UPNumber(intinitValue):value(initValue) {cout<<"UPNumber(intinitValue)"<<endl;}
UPNumber(constUPNumber& rhs) {this->value=rhs.value;}
void destroy() const {delete this;}
private:
~UPNumber(){ cout<<"~UPNumber"<<endl;}
int value;
};
Private妨碍了继承和内含,令UPNumber的destructor成为protected并保持其constructors为public,便可解决继承问题。对于必须内含UPNumber对象之class,可以修改为内含一个指针,指向UPNumber。如果要作为其他对象的成员可以采用如下指针成员的方式:
class Asset
{
public:
Asset(intinitValue):value(new UPNumber(initValue)) {}
~Asset(){value->destroy();}
private:
UPNumber *value;
};
2.异常处理体系要求所有在栈中的对象的析构函数必须申明为公有
3.许多系统都有的一个事实:程序的地址空间以线性序列组织而成,其中stack(栈)高地址往低地址成长,heap(堆)由低地址往高地址成长。
然而,static对象,不只涵盖明白声明为static的对象,也包括global scope和namespace scope内的对象,这些对象的安置位置视系统而定。绝大多数安置在heap之下。
这样无法区分heap对象和static对象。因此判断一个对象是否在堆中:无法通过“地址对比”来确定对象是在堆上或栈上或其他地方。
4.如果你要绝对而确实地说某个地址是否位于heap之中,就一定得走入不可移植的、因实现系统而异的阴暗角落。所以你最好重新设计软件,避免需要判断某个对象是否位于heap内。
5. 采用抽象混合式基类的技术(abstract mixin base class),我们可以形成一个类,用来为派生类提供“判断某指针是否以operator new分配出来”的能力。
抽象基类:不能被实例化的基类,至少含有一个纯虚函数。
混合类mixin("mix in"):提供一组定义完好的能力,能够与派生类所提供的其他任何功能兼容。
方法如下:
class HeapTracked
{
public:
classMissingAddress {};//异常类
virtual~HeapTracked()=0;
staticvoid* operator new(size_t size);
staticvoid operator delete(void* ptr);
boolisOnHeap() const;
private:
staticlist<const void*> addresses;
};
list<const void*> HeapTracked::addresses;
HeapTracked::~HeapTracked() {}
void* HeapTracked::operator new(size_t size)
{
void*memPtr=::operator new(size);
addresses.push_back(memPtr);
returnmemPtr;
}
void HeapTracked::operator delete(void* ptr)
{
list<voidconst*>::iterator iter=find(addresses.begin(),addresses.end(),ptr);
if(iter!=addresses.end())
{
addresses.erase(iter);
::operatordelete(ptr);
}
else
{
throwMissingAddress();
}
}
bool HeapTracked::isOnHeap() const
{
constvoid* rawAddress=dynamic_cast<const void*>(this);
list<constvoid*>::iterator iter=find(addresses.begin(),addresses.end(),rawAddress);
returniter!=addresses.end();
}
class Asset:public HeapTracked
{
public:
Asset(intinitValue) {}
};
调用的时候:
1. Asset s(10);
2. if(s.isOnHeap())
3. cout<<"is on heap"<<endl;
4. else
5. cout<<"is not on heap"<<endl;
以上则输出is not on heap
如下调用:
1. Asset* p=new Asset(1);
2. if(p->isOnHeap())
3. cout<<"is on heap"<<endl;
4. else
5. cout<<"is not on heap"<<endl;
6. delete p;
则输出is on heap。
解释其中一句代码:
const void* rawAddress=dynamic_cast<constvoid*>(this);
把一个指针dynamic_cast成void*类型(或const void*或volatile void*等),生成的指针将指向“原指针指向对象内存的开始处”。但是dynamic_cast只能用于指向至少具有一个虚拟函数的对象的指针上
6.禁止堆对象:通常对象的建立这样三种情况,对象被直接实例化;对象做为派生类的基类被实例化;对象被嵌入到其他对象内
7.禁止用户直接实例化对象很简单,因为总是调用new来建立这种对象,你能够禁止用户调用new。你不能影响new操作符的可用性,但是你能够利用new操作符总是调用operator new函数这点,来达到目的。你可以声明这个函数,而且你可以把它声明为private。但operator new是private这一点,不会对包含该类成员对象的分配产生任何影响。但是仍然不能判断其是否在堆中
class UPNumber
{
public:
UPNumber():value(0){cout<<"UPNumber"<<endl;}
UPNumber(intinitValue):value(initValue) {cout<<"UPNumber(intinitValue)"<<endl;}
UPNumber(constUPNumber& rhs) {this->value=rhs.value;}
~UPNumber(){ cout<<"~UPNumber"<<endl;}
private:
staticvoid* operatornew(size_t size);
staticvoid operatordelete(void* ptr);
int value;
};
如果有类从UPNumber派生,如果不再声明使用全局的operator new和operator delete或者声明为public,则派生类也不能在heap中产生对象。当UPNumber作为其他类的内嵌对象,则不能阻止。
class Asset
{
public:
Asset(intinitValue):value(initValue) {}
private:
UPNumbervalue;
};
如果Asset* p=new Asset(10);则不能阻止对象产生于heap。
8.我们无法判断一个地址是否位于heap,我们也无法判断一个地址是否不在heap内。
条款28:智能(smart)指针
1. 智能指针是一种外观和行为都被设计成与内建指针相类似的对象,不过它能提供更多的功能。它们有许多应用的领域,包括资源管理和重复代码任务的自动化
2. 智能指针从模板中生成,因为要与内建指针类似,必须是强烈的类类型strongly typed(强类型)的;模板参数确定指向对象的类型
3.每个smart pointer-to-T都内含有一个dumb pointer-to-T,后者才是实践指针行为的真正主角。
4.智能指针的拷贝和赋值,采取的方案是当auto_ptr被拷贝和赋值时,对象所有权随之被传递,此时,通过传值方式传递智能指针对象将导致不确定的后果,应该使用引用
5.解引操作符。
Type& operator*( ) const throw( ); 返回的是对象,返回的是reference。记住当返回类型是基类而返回对象实际上派生类对象时,不能传递对象,应该传递引用或指针,否则将产生对象切割
Type *operator->( ) const throw( ); 返回的是指针
这使得一个auto_ptr用起来像一个普通的指针。
1. int _tmain(int argc, _TCHAR* argv[])
2. {
3. auto_ptr<Test> spTest1(new Test(10));
4. spTest1->printValue(); //返回的是指针
5. (*spTest1).printValue(); //返回的是对象
6. return 0;
7. }
6.测试智能指针是否为NULL有两种方案:一种是使用类型转换,将其转换为void*,但是这样将导致类型不安全,因为不同类型的智能指针之间将能够互相比较;另一种是重载operator!,这种方案只能使用!ptr这种方式检测
7.最好不要提供转换到内建指针的隐式类型转换操作符,直接提供内建指针将破坏智能指针的智能特性
8.智能指针的继承类到基类的类型转换的一个最佳解决方案是使用模板成员函数,这将使得内建指针所有可以转换的类型也可以在智能指针中进行转换但是对于间接继承的情况,必须用dynamic_cast指定其要转换的类型是直接基类还是间接基类
9.为了实现const智能指针,可以新建一个类,该类从非const智能指针继承这样的化,const智能指针能做的,非const智能指针也能做,从而与标准形式相同。类型转换如果涉及const,便是一条单行道:从non-const转换至const是安全的,从const转换至non-const则不安全。
条款29:引用计数
1. 使用引用计数改善效率的最适当时机:
(1)相对多数的对象共享相对少量的实值。使用引用计数后,对象自己拥有自己,当没有人再使用它时,它自己自动销毁自己因此,引用计数是个简单的垃圾回收体系
(2)对象实值的产生或销毁的成本很高。最好是让所有等值对象共享一份值就好。
2.引用次数是要为每一个字符串值准备一个引用次数,而不是每一个字符串对象准备。
3.技巧:将一个struct嵌套放进一个class的private段落内,可以很方便地让该class的所有members有权处理这个struct,而又能禁止任何其他人访问这个struct(当然,class的friends不再此限)。
4.在基类中调用delete this将导致派生类的对象被销毁。
5.copy-on-write写时拷贝:对于[]操作符应该悲观的认为所有调用都用于写操作。与其它对象共享一个值直到写操作时才拥有自己的拷贝它是Lazy原则的特例
6.一个reference-counting基类:RCObject:
头文件:
1. class RCObject
2. {
3. protected:
4. RCObject(void);
5. RCObject(const RCObject& rhs);
6. RCObject& operator=(const RCObject& rhs) {return *this;}
7. virtual ~RCObject()=0; //纯虚函数,表示此class只被设计用来作为base class
8. public:
9. void addReference();
10. void removeReference();
11. void markUnshareable();
12. bool isShareable() const;
13. bool isShared() const;
14. private:
15. int refCount;
16. bool shareable;
17. };
实现文件:
1. RCObject::RCObject(void):refCount(0),shareable(true)
2. {
3. }
4.
5. RCObject::RCObject(const RCObject&):refCount(0),shareable(true)
6. {
7. }
8.
9. RCObject::~RCObject(void) //virtual dtors必须被实现出来,即使他们是纯虚函数而且不做任何事情
10. {
11. }
12.
13. void RCObject::addReference()
14. {
15. ++refCount;
16. }
17.
18. void RCObject::removeReference()
19. {
20. if(--refCount==0)
21. delete this;
22. }
23.
24. void RCObject::markUnshareable()
25. {
26. shareable=false;
27. }
28.
29. bool RCObject::isShareable() const
30. {
31. return shareable;
32. }
33.
34. bool RCObject::isShared() const
35. {
36. return refCount>1; //为了简化事情,使对象创建者自行将refcount设为1,所以此处不得不如此
37. }
可以注意,构造函数和拷贝构造函数的refCount都是设为0的,refcount的创建者有责任为refcount设定适当的值。其他的改动引用计数的指责交给了智能指针。
1. #include "RCObject.h"
2.
3. template <typename T>
4. class RCPtr
5. {
6. public:
7. RCPtr(T* realPtr=0);
8. RCPtr(const RCPtr& rhs);
9. ~RCPtr(void);
10.
11. RCPtr& operator=(const RCPtr& rhs);
12. const T* operator->() const;
13. T* operator->();
14. const T& operator*() const;
15. T& operator*();
16. private:
17. struct CountHolder:public RCObject
18. {
19. ~CountHolder() {delete pointee;}
20. T* pointee;
21. };
22. CountHolder* counter;
23. void init(); //共同的初始化动作
24. void makeCopy();
25. };
26.
27. template <typename T>
28. void RCPtr<T>::init()
29. {
30. if(counter->isShareable()==false)
31. {
32. T* oldValue=counter->pointee;
33. counter=new CountHolder();
34. counter->pointee=new T(*oldValue);
35. }
36.
37. counter->addReference();
38. }
39.
40. template <typename T>
41. RCPtr<T>::RCPtr(T* realPtr):counter(new CountHolder())
42. {
43. counter->pointee=realPtr;
44. init();
45. }
46.
47. template <typename T>
48. RCPtr<T>::RCPtr(const RCPtr& rhs):counter(rhs.counter)
49. {
50. init();
51. }
52.
53.
54. template <typename T>
55. RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)
56. {
57. if(counter!=rhs.counter)
58. {
59. counter->removeReference();
60. counter=rhs.counter;
61. init();
62. }
63.
64. return *this;
65. }
66.
67. template <typename T>
68. RCPtr<T>::~RCPtr()
69. {
70. counter->removeReference();
71. }
72.
73. template <typename T>
74. const T* RCPtr<T>::operator->() const
75. {
76. return counter->pointee;
77. }
78.
79. template <typename T>
80. const T& RCPtr<T>::operator*() const
81. {
82. return *(counter->pointee);
83. }
84.
85. template <typename T>
86. void RCPtr<T>::makeCopy()
87. {
88. T* oldValue=counter->pointee;
89. counter=new CountHolder();
90. counter->pointee=new T(*oldValue);
91. counter->addReference();
92. }
93.
94. template <typename T>
95. T* RCPtr<T>::operator->()
96. {
97. if(counter->isShared())
98. {
99. makeCopy();
100. counter->markUnshareable();
101. }
102. return counter->pointee;
103. }
104.
105. template <typename T>
106. T& RCPtr<T>::operator*()
107. {
108. if(counter->isShared())
109. {
110. makeCopy();
111. counter->markUnshareable();
112. }
113. return *(counter->pointee);
114. }
如果我们对现有库中的某个类采用引用计数的方式,则可以采用如下的方式:
Widget和RCWidget的代码如下:
1. #include <iostream>
2. using namespace std;
3.
4. class Widget
5. {
6. public:
7. Widget(int s):size(s) {}
8. Widget(const Widget& rhs) {size=rhs.size;}
9. ~Widget(void) {}
10.
11. Widget& operator=(const Widget& rhs)
12. {
13. if(this==&rhs)
14. return *this;
15.
16. this->size=rhs.size;
17. return *this;
18. }
19. void doThis() {cout<<"doThis()"<<endl;}
20. int showThat() const {cout<<"showThat()"<<endl;return size;}
21. private:
22. int size;
23. };
1. #include "RCPtr.h"
2. #include "Widget.h"
3.
4. class RCWidget
5. {
6. public:
7. RCWidget(int size):value(new Widget(size)) {}
8. ~RCWidget(void) {}
9.
10. void doThis() {value->doThis();}
11. int showThat() const {return value->showThat();}
12. private:
13. RCPtr<Widget> value;
14. };
这样的技术就实现了对已有库中类的引用技术的包装。
即使我们自己去写,也可以实现一个类,用这样的技术去实现引用计数。
我对这部分内容理解的不是很好,先记录下来,慢慢品味。
6.精彩的类层次结构:
RCObject类提供计数操作;StringValue包含指向数据的指针并继承RCObject的计数操作;RCPtr是一个智能指针,封装了本属于String的一些计数操作
条款30:代理类
1.数组的尺度必须在编译期已知,C++甚至不允许一个与二维数组相关的heap-based分配行为:int *data=newint[dim1][dim2].没有根本没有operator[][].
2.可以用两个类来实现二维数组:Array1D是一个一维数组,而Array2D则是一个Array1D的一维数组Array1D的实例扮演的是一个在概念上不存在的一维数组,它是一个代理类。
阻止只有一个参数的类执行隐式类型转换的方法。
1. template <typename T>
2. class Array
3. {
4. public:
5. class ArraySize
6. {
7. public:
8. ArraySize(int numElements):theSize(numElements) {}
9. int size() const {return theSize;}
10. private:
11. int theSize;
12. };
13.
14. Array(ArraySize size);
15. };
其中ArraySize就是数组大小的一个代理类。
Array<int> a(10);
Array<int> b(10);在构造的过程中,编译器将int通过隐式类型转换转为ArraySize的一个临时对象,然后调用Array<int>的构造函数。
如果出现这样的代码:
if(a==b[i]) //a的类型为array<int>,而b[i]的类型为int,则若将int转为一个临时的ArraySize,然后根据ArraySize再构造一个临时的array<int>,这样的行为编译器不允许,不会连续执行两次以上的隐式类型转换。若不采用代理类,则直接根据int就可以构造一个Array<int>可以编译通过。
3.代理类最神奇的功能是区分通过operator[]进行的是读操作还是写操作,它的思想是对于operator[]操作,返回的不是真正的对象,而是一个 proxy类,这个代理类记录了对象的信息,将它作为赋值操作的目标时,proxy类扮演的是左值,用其它方式使用它,proxy类扮演的是右值用赋值 操作符来实现左值操作,用隐式类型转换来实现右值操作.
读取动作是所谓的左值运用rvalue usages;写动作是所谓的左值运用lvalue usages。一个对象作为lvalue,意思是它可以被修改,而以之作为rvalue的意思是它不能被修改。
4.用proxy类区分operator[]作左值还是右值的局限性:要实现proxy类和原类型的无缝替代,必须申明原类型的一整套操作符;另外,使用proxy类还有隐式类型转换的所有缺点。
编程点滴:不能将临时对象绑定为非const的引用的行参
5.区分operator[]的读写操作。
采用带引用计数、使用代理类的String做例子:
RCObject同上篇。
RCPtr如下:
1. template <typename T>
2. class RCPtr
3. {
4. public:
5. RCPtr(T* realPtr=0);
6. RCPtr(const RCPtr& rhs);
7. ~RCPtr(void);
8.
9. RCPtr& operator=(const RCPtr& rhs);
10. T* operator->() const;
11. T& operator*() const;
12. private:
13. T* pointee;
14. void init();
15. };
16.
17. template <typename T>
18. void RCPtr<T>::init()
19. {
20. if(pointee==nullptr) return;
21.
22. if(pointee->isShareable()==false)
23. {
24. pointee=new T(*pointee);
25. }
26.
27. pointee->addReference();
28. }
29.
30. template <typename T>
31. RCPtr<T>::RCPtr(T* realPtr):pointee(realPtr)
32. {
33. init();
34. }
35.
36. template <typename T>
37. RCPtr<T>::RCPtr(const RCPtr& rhs):pointee(rhs.pointee)
38. {
39. init();
40. }
41.
42. template <typename T>
43. RCPtr<T>::~RCPtr()
44. {
45. if(pointee)
46. pointee->removeReference();
47. }
48.
49. template <typename T>
50. RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)
51. {
52. if(pointee!=rhs.pointee)
53. {
54. if(pointee)
55. pointee->removeReference();
56. pointee=rhs.pointee;
57. init();
58. }
59.
60. return *this;
61. }
62.
63. template <typename T>
64. T& RCPtr<T>::operator*() const
65. {
66. return *pointee;
67. }
68.
69. template <typename T>
70. T* RCPtr<T>::operator->() const
71. {
72. return pointee;
73. }
String头文件:
1. #include <iostream>
2. using namespace std;
3.
4. #include "RCObject.h"
5. #include "RCPtr.h"
6.
7. class String
8. {
9. public:
10. class CharProxy
11. {
12. public:
13. CharProxy(String& str,int index):theString(&str),charIndex(index) {}
14. CharProxy& operator=(const CharProxy& rhs);
15. CharProxy& operator=(char c);
16.
17. char* operator&();
18. const char* operator&() const;
19.
20. operator char() const;
21. private:
22. void assignment(char c);
23. String* theString;
24. int charIndex;
25. };
26.
27. friend class CharProxy;
28.
29. String(const char* value="");
30. const CharProxy operator[](int index) const;
31. CharProxy operator[](int index);
32. private:
33. struct StringValue:public RCObject
34. {
35. char* data;
36. StringValue(const char* initValue);
37. StringValue(const StringValue& rhs);
38. void init(const char* initValue);
39. ~StringValue();
40. };
41. RCPtr<StringValue> value;
42. friend ostream& operator<<(ostream& os,const String& str)
43. {
44. os<<str.value->data;
45. return os;
46. }
47. };
String实现文件:
1. #include "String.h"
2.
3. String::CharProxy::operator char() const
4. {
5. return theString->value->data[charIndex];
6. }
7.
8. void String::CharProxy::assignment(char c)
9. {
10. if(theString->value->isShared())
11. theString->value=new StringValue(theString->value->data);
12. theString->value->data[charIndex]=c;
13. }
14.
15. String::CharProxy& String::CharProxy::operator=(const String::CharProxy& rhs)
16. {
17. assignment(rhs.theString->value->data[charIndex]);
18. return *this;
19. }
20.
21. String::CharProxy& String::CharProxy::operator=(char c)
22. {
23. assignment(c);
24. return *this;
25. }
26.
27. const char* String::CharProxy::operator&() const
28. {
29. return &(theString->value->data[charIndex]);
30. }
31.
32. char* String::CharProxy::operator&()
33. {
34. if(theString->value->isShared())
35. theString->value=new StringValue(theString->value->data);
36.
37. theString->value->markUnshareable();
38. return &(theString->value->data[charIndex]);
39. }
40.
41. void String::StringValue::init(const char* initValue)
42. {
43. data=new char[strlen(initValue)+1];
44. strcpy_s(data,strlen(initValue)+1,initValue);
45. }
46.
47. String::StringValue::StringValue(const char* initValue)
48. {
49. init(initValue);
50. }
51.
52. String::StringValue::StringValue(const StringValue& rhs)
53. {
54. init(rhs.data);
55. }
56.
57. String::StringValue::~StringValue()
58. {
59. delete[] data;
60. }
61.
62. String::String(const char* initValue):value(new StringValue(initValue))
63. {
64.
65. }
66.
67. const String::CharProxy String::operator[](int index) const
68. {
69. return CharProxy(const_cast<String&>(*this),index);
70. }
71.
72. String::CharProxy String::operator[](int index)
73. {
74. return CharProxy(*this,index);
75. }
例子分析:
1. int _tmain(int argc, _TCHAR* argv[])
2. {
3. String str1="Hello";
4. String str2="How many!!";
5.
6. cout<<str1[2]<<endl;
7. str2[5]='y';
8. return 0;
9. }
在cout<<str1[2]<<endl;这句中,首先通过CharProxy operator[](intindex);返回一个CharPorxy对象,然后调用operator char() const;转为char型,然后输出。
在str2[5]='y';这句中,首先通过CharProxy operator[](intindex);返回一个CharProxy对象,然后调用CharProxy&operator=(char c);来完成操作。
因此这样就通过代理类的两个成员分清楚了是读操作,还是写操作。
7.代理类的存在也增加了软件系统的复杂度。
条款31:让函数根据一个以上的对象来决定怎么虚拟
1.有三种方式:用虚函数加RTTI,在派生类的重载虚函数中使用if-else对传进的不同类型参数执行不同的操作,这样做几乎放弃了封装,每增加一个新的类型时,必须更新每一个基于RTTI的if-else链以处理这个新的类型,因此程序本质上是没有可维护性的,欲对这种程序再扩充,基本上是很麻烦的。这便是虚函数当初被发明的主要原因:把生产及维护“以类型”;只使用虚函数,通过几次单独的虚函数调用,第一次决定第一个对象的动态类型,第二次决定第二个对象动态类型,如此这般。然而,这种方法的缺陷仍然是:每个类必须知道它的所有同胞类,增加新类时,所有代码必须更新;模拟虚函数表,在类外建立一张模拟虚函数表,该表是类型和函数指针的映射,加入新类型是不须改动其它类代码,只需在类外增加一个处理函数即可
2.匿名指namespace内的每样东西对其所驻在的编译单元(文件)而言都是私有的,其效果就好像在文件里头将函数声明为static一样。所以,文件生存空间内的statics已经不再继续被鼓励使用。函数声明和实现都必须在相同的namespace里,这样连接器才能够正确将其定义和稍早出现的声明关联在一起。
3、make_pair是标准程序库提供的一个十分便利的function template,可以免除在构造一个pair对象时必须指定类型的麻烦。
2. 假设要做一个太空中各种天体的碰撞,其中涉及宇宙飞船、太空战、小行星等天体。
定义如下:
1. class GameObject
2. {
3. public:
4. virtual void collideResult()=0;
5. virtual ~GameObject()=0;
6. };
7.
8. GameObject::~GameObject() { }
9.
10. class SpaceShip:public GameObject
11. {
12. public:
13. void collideResult()
14. {
15. cout<<"SpaceShip::collideResult()"<<endl;
16. }
17. };
18.
19. class SpaceStation:public GameObject
20. {
21. public:
22. void collideResult()
23. {
24. cout<<"SpaceStation::collideResult()"<<endl;
25. }
26. };
27.
28. class Asteroid:public GameObject
29. {
30. public:
31. void collideResult()
32. {
33. cout<<"Asteroid::collideResult()"<<endl;
34. }
35. };
collideResult是各种不同的天体碰撞后各自产生的后果。
采取的方法是:使用“非成员函数”的碰撞处理函数
1. void ProcessCollision(GameObject* object1,GameObject* object2)
2. {
3. string type1=typeid(*object1).name();
4. string type2=typeid(*object2).name();
5. CollisionMap::HitFunctionPtr ptr=CollisionMap::theCollisionMap()->lookup(type1,type2);
6. if(ptr!=NULL)
7. (*ptr)(*object1,*object2);
8. }
上函数中的CollisionMap就是存放游戏对象和相应的碰撞处理函数的map结构,仿真的是虚函数表。
1. class CollisionMap
2. {
3. public:
4. typedef void (*HitFunctionPtr)(GameObject&,GameObject&);
5. typedef map<pair<string,string>,HitFunctionPtr> HitMap;
6.
7. void addEntry(const string& type1,const string& type2,HitFunctionPtr collisionFunction,bool sysmetric=true);
8. void removeEntry(const string& type1,const string& type2);
9. HitFunctionPtr lookup(const string& type1,const string& type2);
10. static CollisionMap* theCollisionMap();
11. private:
12. CollisionMap() {}
13. CollisionMap(const CollisionMap&);
14. static HitMap collisionMap;
15.
16. pair<string,string> makeStringPair(const string& str1,const string& str2);
17. };
18.
19. CollisionMap::HitMap CollisionMap::collisionMap;
20.
21. CollisionMap* CollisionMap::theCollisionMap()
22. {
23. static CollisionMap cm;
24. return &cm;
25. }
26.
27. pair<string,string> CollisionMap::makeStringPair(const string& str1,const string& str2)
28. {
29. return make_pair(str1,str2);
30. }
31.
32. void CollisionMap::addEntry(const string& type1,const string& type2,HitFunctionPtr collisionFunction,bool sysmetric)
33. {
34. pair<string,string> hitObjects=makeStringPair(type1,type2);
35. collisionMap.insert(pair<pair<string,string>,HitFunctionPtr>(hitObjects,collisionFunction));
36. if(sysmetric)
37. {
38. pair<string,string> hitObjects=makeStringPair(type2,type1);
39. collisionMap.insert(pair<pair<string,string>,HitFunctionPtr>(hitObjects,collisionFunction));
40. }
41. }
42.
43. void CollisionMap::removeEntry(const string& type1,const string& type2)
44. {
45. pair<string,string> hitObjects=makeStringPair(type1,type2);
46. HitMap::iterator iter=collisionMap.find(hitObjects);
47. if(iter!=collisionMap.end())
48. collisionMap.erase(iter);
49. }
50.
51. CollisionMap::HitFunctionPtr CollisionMap::lookup(const string& type1,const string& type2)
52. {
53. pair<string,string> hitObjects=makeStringPair(type1,type2);
54. HitMap::iterator iter=collisionMap.find(hitObjects);
55. if(iter!=collisionMap.end())
56. return (*iter).second;
57. else
58. return NULL;
59. }
因为一个系统中,只有一个这样的对象函数表,采用的是单实例的设计模式。
为了防止在这个函数表使用之前一定得到初始化,采用如下的措施:
1. class RegisterCollisionFunction
2. {
3. public:
4. RegisterCollisionFunction(const string& type1,const string& type2,CollisionMap::HitFunctionPtr collisionFunction,bool sysmetric=true)
5. {
6. CollisionMap::theCollisionMap()->addEntry(type1,type2,collisionFunction,sysmetric);
7. }
8. };
9.
10. void shipAsteroid(GameObject& spaceShip,GameObject& asteroid)
11. {
12. spaceShip.collideResult();
13. asteroid.collideResult();
14. cout<<"SpaceShip collide with Asteroid"<<endl;
15. }
16.
17. void shipStation(GameObject& spaceShip,GameObject& station)
18. {
19. spaceShip.collideResult();
20. station.collideResult();
21. cout<<"Spaceship collide with SpaceStation"<<endl;
22. }
23.
24. void asteroidStation(GameObject& asteroid,GameObject& station)
25. {
26. asteroid.collideResult();
27. station.collideResult();
28. cout<<"Asteroid collide with SpaceStation"<<endl;
29. }
30.
31. void shipShip(GameObject& spaceShip1,GameObject& spaceShip2)
32. {
33. spaceShip1.collideResult();
34. spaceShip2.collideResult();
35. cout<<"SpaceShip collide with Space Ship"<<endl;
36. }
37.
38. void asteroidAsteroid(GameObject& asteroid1,GameObject& asteroid2)
39. {
40. asteroid1.collideResult();
41. asteroid2.collideResult();
42. cout<<"Asteroid collide with Asteroid"<<endl;
43. }
44.
45. void stationStation(GameObject& station1,GameObject& station2)
46. {
47. station1.collideResult();
48. station2.collideResult();
49. cout<<"SpaceStation collide with Collide"<<endl;
50. }
51.
52. RegisterCollisionFunction cf1("class SpaceShip","class Asteroid",&shipAsteroid);
53. RegisterCollisionFunction cf2("class SpaceShip","class SpaceStation",&shipStation);
54. RegisterCollisionFunction cf4("class SpaceShip","class SpaceShip",&shipShip);
55. RegisterCollisionFunction cf3("class Asteroid","class SpaceStation",&asteroidStation);
56. RegisterCollisionFunction cf5("class Asteroid","class Asteroid",&asteroidAsteroid);
57. RegisterCollisionFunction cf6("class SpaceStation","class SpaceStation",&stationStation);
因为这些碰撞函数都是全局变量,一定会在进入main函数之前初始化。
测试程序如下:
1. int _tmain(int argc, _TCHAR* argv[])
2. {
3. SpaceShip object1;
4. SpaceStation object2;
5. ProcessCollision(&object1,&object2);
6.
7. return 0;
8. }
在处理函数中,首先调用第一个对象的collisionResult,然后调用第二个对象的collisionResult,然后提示哪两个对象发生了碰撞。
至此,采用这样的技术实现了让ProcessCollision函数根据两个对象的动态类型实现虚化。
根据多个参数实现虚化的技术类似。