More Effective C++ 第五部分 技术
来源:互联网 发布:linux创建新文件 编辑:程序博客网 时间:2024/05/24 05:05
本部分内容比较高深,涉及很多较高端的技术。笔记会有许多不足的地方,若复习可以在看一遍书。只看笔记恐怕无法彻底理解。
25.将constructor和non-member function虚化
virtual constructor
virtual constructor是并非是constructor,其根据输入可产生不同类型的对象(以多态的方式返回),常用的使用情况如在磁盘读取数据,产生一个或为音乐,或为视频的对象。
virtual copy constructor
virtual copy constructor 返回一个指针,指向其调用者的一个新副本,通常命名为colne().使用virtual copy constructor可以用一个基类指针调用clone()得到一个派生类的返回值。
virtual函数返回类型可与基类中虚函数的类型不一致。
#include <iostream>#include <vector>class Base{public: virtual Base* clone() const = 0;};class Derived1: public Base{public: //virtual copy constructor返回类型不为基类类型 virtual Derived1* clone() const{ return new Derived1(*this); } int a = 1;};class Derived2: public Base{public: virtual Derived2* clone() const{ return new Derived2(*this); } int b = 2;};int main(int argc, const char * argv[]) { std::vector<Base*> v; v.push_back(new Derived1()); v.push_back(new Derived2()); std::vector<Base*> v2; for (auto i = v.begin(); i!=v.end(); i++) { v2.push_back((*i)->clone());//调用virtual copy constructor } //此时v2第一个内容应该是derived1类型的,第二个是derived2类型的 printf("%d %d", dynamic_cast<Derived1*>(*(v2.begin()))->a, dynamic_cast<Derived2*>(*(v2.begin()+1))->b ); return 0;}输出:1 2Program ended with exit code: 0
将non-member function的行为虚化
指在一个non-member function中调用一个虚函数。不做其他的事情。出现情况例如下边的例子:
若使用虚函数operator<<将会导致t << cout;
这样的奇怪写法,因为operator<<接受一个类型为ostream&的右边的值。而不能接受左边的值。
class NLComponent {public: // 对输出操作符的不寻常的声明 virtual ostream& operator<<(ostream& str) const = 0; ...};class TextBlock: public NLComponent {public: // 虚拟输出操作符(同样不寻常) virtual ostream& operator<<(ostream& str) const;};class Graphic: public NLComponent {public: // 虚拟输出操作符 virtual ostream& operator<<(ostream& str) const;};TextBlock t;Graphic g;...t << cout;//不能写cout<<t,因为类内声明的operator<<接受一个右边的ostream&g << cout;
其解决办法是
class NLComponent {public: virtual ostream& print(ostream& s) const = 0; };class TextBlock: public NLComponent {public: virtual ostream& print(ostream& s) const;};class Graphic: public NLComponent {public: virtual ostream& print(ostream& s) const; };//虚化的non-member functioninlineostream& operator<<(ostream& s, const NLComponent& c){ //仅调用虚函数 return c.print(s);} TextBlock t; Graphic g; cout << t; cout << g;//可以使用正常的写法 return 0;
26.限制某个class所能产生的对象数量
阻止产出对象
将各constructor声明为private
仅产生一个对象
使用单例模式,但书中提出一个观点是将static对象声明在function内。
class Printer{public: static Printer& thePrinter();private: Printer(); Printer(const Printer&);};Printer& Printer::thePrinter(){ static Printer p;//使用Function static return p;}
使用Function static的好处是:
1.Function static在函数调用时才会构造对象,而class static则在程序开始时被创建。也就意味着即使没有使用到也会被创建。
2.C++对不同编译单元内的static对象初始化顺序不提供任何说明,使用class static可能会导致一些问题。
使用Function static不可将其inline,因为目标代码可能会复制多份local static对象。
不同的对象构造状态
在三种情况下会调用构造函数:
1.正常的构造。
2.派生类的base class部分
3.内嵌于其他对象内。
在控制对象产生的数量时应注意。
限制对象数量的方法
在class内声明一个static成员,用来指定生成对象最多的数量,在产生对象的函数内进行判断,若超过最大限制,则抛出异常:例如tooManyObj.
我们可以将这部分功能分离出来形成一个base class,将其template化,在使用的时候使用private继承,利用base class的构造函数在derived class构造函数内被调用的特性实现自动的管理。实现如下:
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(numbObjects >= maxObjects) throw TooManyObjects(); ++numObjects;}//初始化static对象template<class BeingCounted> int Counted<BeingCounted>::numObjects;
使用方法:
class Printer: private Counted<Printer>{public://若Printer类的用户想知道当前有多少个对象,可以把让objectCount成为public的。 using Counted<Printer>::objectCount;};//初始化static对象const size_t Counted<Printer>::maxObjects = 10;
27.要求(或禁止)对象产生于heap之中
要求对象产生于heap之中
产生于heap 对象必须使用new.
限制constructors和destructor其中之一为非public即可,选用限制destructor的原因是一个class有多个constructor,若由编译器合成便为public,所以我们选用限制destructor。将destructor声明为protected而非private的原因是在继承和内涵的情况下,不可以声明private。若要调用destructor需要一个member function间接调用。
class UPNumber{public: void destory() const{ delete this; }protected: ~UPNumber();};
若以产生非heap对象将会在自动析构时报错,因为无法访问destructor.
阻止对象产生于heap之中
使用new产生的对象需要调用operator new,可以把operator new声明为private以阻止。
class UPNumber{private: static void* operator new(size_t size); static void operator delete(void* ptr);//最好声明为与new同一访问层级. //还可以声明operator new[]};
这样继承自该类的对象也无法产生heap内的对象,但内含该class的可以,因为其调用的是自身的operator new.
28.智能指针
智能指针包含一个普通指针数据,通过构造析构重载操作符等方法实现“智能”并且与原指针使用方法类似。
实现解引操作符
解引操作符包含operator*和operator->
operator*需要返回一个引用。若返回T类型对象,若装入的是T类型的派生类则会发生切割问题。
template<class T>T& SmarePtr<T>::operator*() const{ .... return *pointee;//pointee是原始指针}
operator->返回一个普通指针即可,因为
//pt是智能指针,重载了operator->pt->fun();//会变为(pt.operator->())->fun();
所以operator->实现如下
template<class T>T* SmarePtr<T>::operator->() const{ .... return pointee;//pointee是原始指针}
测试智能指针是否为空
希望可以使用传统指针判空的方式:
if(pt == 0);if(pt);if(!pt);
采用的办法是重载operator void*(),提供隐式转换为void指针
template<class T>class SmartPtr{public: operator void*();//如果普通指针为空返回0,否则返回非零值。};
其缺点是可以比较两个不同类型的智能指针。因为都将其转为void*.
不要提供对普通指针的隐式转换
提供对普通指针的隐式转换将会导致可以轻易的对普通指针操作,从而回避了智能指针的设计目的。
智能指针与“与继承有关的”类型转换
使用template function实现
template<class newType>operator SmartPtr<newType>(){ return SmartPtr<newType>(pointee);}
该方法的缺点是若有一个函数接收基类的智能指针,还有一个重载版本,接收派生类的智能指针。将会导致二义性,不能通过编译。
29.引用计数Reference counting
引用计数带来两个好处:
1.实现垃圾回收机制
2.使等值对象共享一份实值
等值对象共享一份实值
使等值对象共享一份实质,需要记录被共享的实值被共享的次数。
做法是使用一个struct将引用计数与实值关联起来
class String {public: String(const char *initValue = "") : value(new StringValue(initValue)) {} String(const String& rhs) :value(rhs.value) { ++value->refCount; } ~String() { if (--value->refCount == 0) delete value; } String& operator=(const String& rhs) { if (value == rhs.value) { return *this; } if (--value->refCount == 0) { delete value; } value = rhs.value; ++value->refCount; return *this; } //const string的operator[]假设只读,详细见下章 const char& operator[](int index) const { return value->data[index]; } //非const string的operator[]可读可写 char& operator[](int index) { if (value->refCount > 1) { --value->refCount;//减少引用1个 value = new StringValue(value->data);//创建一个新的stringvalue } return value->data[index]; }private: struct StringValue { int refCount; char *data; StringValue(const char *initValue); ~StringValue(); }; StringValue *value;};String::StringValue::StringValue(const char *initValue):refCount(1){ data = new char[strlen(initValue) + 1]; strcpy(data, initValue);}String::StringValue::~StringValue(){ delete [] data;}
这个做法有一个缺点:
string s = "fuck"; char* p = &s[1]; auto s2 = s;//s2和s共享一个实值 *p = 'b';//检测不到实值被改变。s2也被改变
解决方法是使用一在StringValue内设置一个shareable标志,一旦non-const operator[]作用于对象值身上便将此标志设为false,并且不可改变。所有其他的member function都应检查sharedable。实现将在下面的基类给出。
引用计数基类
class RCObject{public: RCObject(); RCObject(const RCObject& rhs); RCObject& operator=(const RCObject& rhs); virtual ~RCObject() = 0;//设计为base class的析构函数为virtual void addReference(); void removeReference(); void markUnshareable(); bool isShareable() const; bool isShared() const;private: int refCount; bool shareable;};RCObject::RCObject(): refCount(0), shareable(true) {}//当RCObject被复制的时候会产生一个新的实值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; }class String {private: struct StringValue: public RCObject { char *data; StringValue(const char *initValue); StringValue(); }; ...};
自动引用计数
各对象通过一个指针共享一个实值,我们可以修改指针(28章)以侦测指针的构造copy等操作,来自动设置共享了实值的对象。
被共享的对象生成在heap内,需要使用27章的方法强制其生成在heap中。
整合
将所有部分整合在一起,结果如图:
各部分详细代码和整合代码参考书203-209页
使用这种技术会导致代码维护难度极大的增加,应确保这样做可以大幅优化程序性能才使用这种方法。
30.替身类、代理类 Proxy classes
Proxy classes用来代表一个观念上并不存在的类。可以完成一些十分困难的任务,但需要从与真实的对象合作,转变到与替身对象合作,其行为有些差异。
例如希望实现一个二维数组,可通过A[9][8]
这样的方式访问。但是并没有operator[][]。所以我们建立一个替身类,二维数组重载operator[],返回该替身类的对象,而该替身类也重载operator[],返回在二维数字内的真正的值。这样operator[][]便看起来的合法的。
Proxy classes区分operator[]左值运用和右值运用
在上一章我们假设const char& operator[](int index) const
只做读操作,在这章我们用Proxy classes解决区分左值运用和右值运用。
我们令string的operator[]返回一个proxy class对象,然后等待其被运用,在确定operator[]是读还是写操作。若对proxy object做赋值动作将会是写操作,其余为读操作。
class String {public: class CharProxy { public: CharProxy(String& str, int index); CharProxy& operator=(const CharProxy& rhs);//左值运用 CharProxy& operator=(char c);//左值运用 operator char() const;//右值运用 private: String& theString; int charIndex; }; const CharProxy operator[](int index) const; // 返回替身对象 CharProxy operator[](int index); // 返回替身对象 friend class CharProxy;private: RCPtr<StringValue> value;};
考虑以下调用方式
String s1,s2;cout<<s1[5];//s1[5]产生一个CharProxy对象,但该对象并无<<操作符,//但是可以提供到char的隐式转换,所有可通过该隐式转换判定为右值运用s2[5] = 'x';//s2[5]产生一个CharProxy对象,调用operator=,可判定为左值运用
限制
1.在判断左值与右值引用时,可能不止一种赋值方式,可以使用+=,-=,++……这意味着若希望proxy class能和返回其对象的类合作,便要支持这些operator。
2.通过proxy class调用原对象member function将会失败,这将导致下边这样的代码报错。除非重载。
s[4].memberFun();
3.无法返回reference to non-const objects对象。
void swap(char& a, char& b);String s = "+C+";swap(s[0], s[1]);
String::operator[]返回一个 CharProxy 对象,但 swap()函数要求它所参数是 char & 类型。一个 CharProxy 对象可以印式地转换为一个 char,但没有转换为 char &的转换函数。 而它可能转换成的 char 并不能成为 swap 的 char &参数,因为这个 char 是一个临时对象。
4.无法提供原对象的隐式类型转换。
31.让函数根据一个以上的对象类型来决定如何虚化
传统虚函数只依据一个对象的动态类型决定调用的函数是哪个,本章将以2或以上个参数的动态类型来决定调用哪个函数,也就是说根据1个以上的对象类型来决定如何虚化函数。
例如构造一个星体碰撞的程序,碰撞的结果由碰撞的两个对象的类型决定。
class GameObject{...};class SpaceShip: public { ... }; class SpaceStation: public GameObject { ... }; class Asteroid: public GameObject { ... }; processCollision(GameObject& object1,GameObject& object2);//处理碰撞结果的函数
虚函数+运行期类型识别RTTI
在base class内定义虚函数,在派生类改写的虚函数内利用RTTI和if else判断两个object的类型
class GameObject { public: virtual void collide(GameObject& otherObject) = 0; };void SpaceShip::collide(GameObject& otherObject) { const type_info& objectType = typeid(otherObject); if (objectType == typeid(SpaceShip)) { SpaceShip& ss = static_cast<SpaceShip&>(otherObject); //处理碰撞 }else if((objectType == typeid(SpaceStaion)){ }//下面省略 //若不是三种类型之一就抛出一个异常}
使用这种方法导致代码维护性增加,类之间的耦合性增大。
只用虚函数
做法是在base class内重载虚函数。
class GameObject { public: virtual void collide(GameObject& otherObject); virtual void collide(SpaceShip& otherObject); vrtuall void collide(SpaceSttion& otherObject); virtual void collide(Asteroid& otherObject); ... }; //处理非三种类型的参数,不像第一种会抛出异常void SpaceShip::collide(GameObject& otherObject) { otherObject.collide(*this); }
仿真虚函数表virtual function table
在class内声明一个函数指针,并实现一个函数lookup,其功能是根据不同的输入对象类型,使函数指针指向处理与该类型对象碰撞的函数。
class SpaceShip: pulic GameObject{private: typedef void (SpaceShip::*HitFunctionPtr)(GameObject&); typedef map<string,HitFunctionPtr> HitMap;//虚函数表的typedef static HitFunctionPtr lookup(const GameObject& whatWeHit); static HitMap initializeCollisionMap();//初始化map //接受GameObject&类型参数。在每个函数的实现内将其dynamic——cast为正确的类型 virtual void hitSpaceShip(GameObject& spaceShip); virtual void hitSpaceStation(GameObject& spaceStation); virtual void hitAsteroid(GameObject& asteroid);};//base class定义的纯虚函数void SpaceShip::collide(GameObject& otherObject){ HitFunctionPtr hfp = lookup(otherObject); if (hfp) { (this->*hfp)(otherObject); } else { throw CollisionWithUnknownObject(otherObject); } } SpaceShip::HitFunctionPtr SpaceShip::lookup(const GameObject& whatWeHit){ static auto_ptr<HitMap> collisionMap(initializeCollisionMap());//初始化,见下面 HitMap::iterator mapEntry = collisionMap.find(typeid(whatWeHit).name()); if(mapEntry == collisionMap.end()) return 0; return (*mapEntry).second;}SpaceShip::HitMap * SpaceShip::initializeCollisionMap(){ HitMap *phm = new HitMap; (*phm)["SpaceShip"] = &hitSpaceShip; (*phm)["SpaceStation"] = &hitSpaceStation; (*phm)["Asteroid"] = &hitAsteroid; return phm;}
使用non-member函数
使用non-member函数的好处是当修改时(增加或删除等)不必修改类,直接修改本文件即可。
#include "SpaceShip.h"#include "SpaceStation.h"#include "Asteroid.h"namespace { // 使用你们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); } //HitFunctionPtr现在是指向non-member function的指针 typedef void (*HitFunctionPtr)(GameObject&, GameObject&); //HitMap变为pair<string,string>, HitFunctionPtr型 typedef map< pair<string,string>, HitFunctionPtr > HitMap; //可使用标准库make_pair代替 pair<string,string> makeStringPair(const char *s1, const char *s2); //需要修改以适应新HitMap类型,原理不变 HitMap * initializeCollisionMap(); //需要修改以适应新HitMap类型,原理不变 HitFunctionPtr lookup(const string& class1, const string& class2);} // end namespacevoid processCollision(GameObject& object1, GameObject& object2){ HitFunctionPtr phf = lookup(typeid(object1).name(), typeid(object2).name()); if (phf) phf(object1, object2); else throw UnknownCollision(object1, object2);//查找不到对应的函数则抛出一个异常}
当需要修改时,直接在匿名的namespace加上处理的函数,在 initializeCollisionMap()中添加对应的map即可。
缺点
上述的所有方法都是静态的,没有解耦合,若想增加或删除都需要更改已将写好的函数。使用动态方法的思路是:
我们可以将存储撞击函数的map放入一个类,并由它提供动态修改映射关系的成员函数。
- More Effective C++ 第五部分 技术
- 《More Effective C++》读书笔记-技术
- 《more effective c++》基础部分读书笔记
- 《More Effective C++》读书笔记-技术(二)
- 《more effective c++》读书笔记
- More Effective C++(2)
- 《More Effective C++》读后感
- 《More Effective C++》笔记
- More Effective(五)技术(一)
- More Effective(五)技术(二)
- 《Effective C++》和《More Effective C++》汇总
- 《More Effective C++》读书笔记一
- 《more effective c++》笔记4
- More Effective C++:类型转换
- More Effective C++:类型转换
- More Effective C++:Item 27
- more effective c++--引用计数
- 读More Effective C++(1)
- HoneyComb3.0技术系列之StackView
- 内置函数map.fitler.reduce,偏函数,zip
- Programming Reflection
- poj3723最小生成树
- 什么是DOM?DOM和JavaScript的关系[web开发]
- More Effective C++ 第五部分 技术
- ARM处理器启动流程及bootlader架构设计
- leetcode -- 利用python统计string中的频率问题
- ScrollView中内容自动滑动至底部
- Struts问题: IllegalArgumentException occurred while calling setter of com.zzuli.
- 配置hive1.2.1,并更改元数据库为mysql
- 使用iptables和tc来进行限速
- Web服务器学习(一)
- fastcgi_finish_request