条款26: 尽可能延后变量定义式的出现时间
01 //方案A和B哪个比较好?
02 //方案A
03 Widget w;
04 for (int i = 0; i < n; ++i) {
05 w=...;
06 }
07
08 //方案B
09 for (int i = 0; i < n; ++i) {
10 Widget w(...);
11 }
方案A:1次构造,1次析构,n次赋值
方案B:n次构造,n次析构
除非你知道(1)赋值成本小于构造+析构 (2)你正在处理代码中效率高度敏感的部分
否则应该选择方案B
条款27:尽量少做转型动作
01 (T)expression //C风格
02 T(expression) //函数风格
03
04 //C++新式转型
05 const_cast<t>(expression) //常量性移除,唯一有此能力的转型
06 dynamic_cast<t>(expression) //安全向下转型,唯一无法由旧时语法执行的动作
07 reinterpret_cast<t>(expression)//低级转型,实际动作可能取决于编译器,如int*转为int,条款50。没有看到类型转化兼容性的检查。
08 static_cast<t>(expression) //强迫隐式转型,non-const转const,int转double,void*转typed指针,pointer to base转pointer to derived
09
10 //最好唯一使用旧时转型的时机: 调用explicit构造函数将对象传给函数
11 class Widget {
12 public:
13 explicit Widget(int size);
14 };
15 void doSomeWork(const Widget& w);
16 doSomeWork(Widget(15));
17 doSomeWork(static_cast<widget>(15)); // create Widget from int with C++-style cast
18 </widget></t></t></t></t>
转型不是告诉编译把某种类型视为另一种类型! 任何一个类型转换往往真的另编译器编译出运行期执行的码
1 int x, y;
2 double d = static_cast<double>(x)/y; //用浮点除法
3
4 Dereved d;
5 Base *pb = &d; //Derived* 转为Base*
6 //两个指针指可能不同,可能会有个偏移量offset在运行期被施于Derived指针上,用以取得正确的Base*
7 </double>
单一对象(如一个Derived对象),可能拥有一个以上的地址(如以Base*指向的地址和以Derived*指向的地址)。
因此,避免做出对象在C++中如何布局的稼穑,更不改以此假设为基础执行任何转型动作。如将对象地址转为char*,然后在上面进行指针算术,几乎总会未定义。
01 class Window {
02 public:
03 virtual void onResize() { ... }
04 ...
05 };
06 class SpecialWindow: public Window {
07 public:
08 virtual void onResize() {
09 static_cast<window>(*this).onResize(); //error,在当前对象的base class成分的副本上调用Window::onResize()
10 ... // 当前对象上执行SpecialWindow专属动作
11 }
12 };
13 //解决之道
14 class SpecialWindow: public Window {
15 public:
16 virtual void onResize() {
17 Window::onResize(); // call Window::onResize
18 ... // on *this
19 }
20 ...
21 };
22
23
24 </window>
dynamic_cast通常是你想在一个你认定为derived class对象上执行derived class操作函数,但你手上只有base*或引用。
可使用类型安全容器,或将virtual函数往继承体系上方移动,替换dynamic_cast转型。 不要使用连串dynamic_cast。
条款28: 避免返回handles指向对象内部成分
避免返回handles(包括引用,指针,迭代器)指向内部数据。遵守这个条款可以增加封装性,帮助const成员函数像个const,并将发生悬吊号码牌dangling handles的可能性降至最低。
但不意味绝对不可以让成员函数返回handles。如operator[]允许你摘采string,vector的个别元素,这些数据会随着容器的销毁而销毁。而这样的函数只是例外。
01 class Point {
02 public:
03 Point(int x, int y);
04 void setX(int newVal);
05 void setY(int newVal);
06 ...
07 };
08
09 struct RectData {
10 Point ulhc; // ulhc = " upper left-hand corner"
11 Point lrhc; // lrhc = " lower right-hand corner"
12 };
13
14
15 class Rectangle {
16 ...
17 private:
18 std::tr1::shared_ptr<rectdata> pData;
19 };
20
21 class Rectangle {
22 public:
23 Point& upperLeft() const { return pData->ulhc; }
24 Point& lowerRight() const { return pData->lrhc; }
25 };
26
27 Point coord1(0, 0);
28 Point coord2(100, 100);
29 const Rectangle rec(coord1, coord2);
30 rec.upperLeft().setX(50); //rec是const,upperLeft是const函数,但内部数据被更改了
31
32 //解决方案,向外传递内部数据引用的成员函数申明为const
33 class Rectangle {
34 public:
35 const Point& upperLeft() const { return pData->ulhc; }
36 const Point& lowerRight() const { return pData->lrhc; }
37 }
38 //但依然有问题,可能导致空悬的号码牌dangling handles: handles所指东西(的所属对象)不复存在
39 //考虑下例
40 class GUIObject { ... };
41 const Rectangle boundingBox(const GUIObject& obj);
42
43 GUIObject *pgo;
44
45 const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());
46 //boundingBox返回一个const Rectangle临时对象temp,然后upperLeft返回temp的一个const Point引用
47 //然后另pUpperLeft指向那个Point
48 //但是当这个语句结束后,临时对象temp被销毁,导致temp内的Points析构,pUpperLeft成为悬垂指针
49 </rectdata>
条款29: 为异常安全而努力是值得的。
以对象管理资源管理资源解决资源泄漏,然后根据“现实可施作”条件下挑选3个异常安全保证中的最强烈等级,只有在你的函数调用了传统代码,才别无选择的设为无任何保证。
01 class PrettyMenu {
02 public:
03 void changeBackground(std::istream& imgSrc);
04 private:
05 Mutex mutex;
06 Image *bgImage;
07 int imageChanges;
08 };
09
10 void PrettyMenu::changeBackground(std::istream& imgSrc)
11 lock(&mutex);
12 delete bgImage;
13 ++imageChanges;
14 bgImage = new Image(imgSrc);
15 unlock(&mutex);
16 }
异常安全的两个条件:当异常抛出时
不泄露任何资源: new Image(imgSrc)抛出异常,unlock(&mutex); 就不会执行了
不允许数据败坏: new Image(imgSrc)抛异常, bgImage就指向已被删除的对象,imageChanges也已被增加
1 //避免资源泄露,条款13,14,以对象管理资源
2 void PrettyMenu::changeBackground(std::istream& imgSrc){
3 Lock ml(&mutex);
4 delete bgImage;
5 ++imageChanges;
6 bgImage = new Image(imgSrc);
7 }
异常安全函数提供三个保证:
基本承诺: 如果异常被抛出,程序内任何事物仍然保持在有效状态下。程序有可能处于任何状态--只要该状态合法。
强烈保证: 如抛出异常,程序状态不改变。如果函数成功,就是完全成功;函数是不程序会回复到调用函数之前的状态。
不抛掷(nothrow)保证: 承诺绝不抛出异常,因为它们总是能为完成它们原先承诺的功能
1 int doSomething() throw(); //空白的异常明细,如果抛出异常,则会调用unexpected(),其中调用terminate(),可用set_expected()设置默认函数
01 class PrettyMenu {
02 ...
03 std::tr1::shared_ptr<Image> bgImage;
04 ...
05 };
06 void PrettyMenu::changeBackground(std::istream& imgSrc) {
07 Lock ml(&mutex);
08 bgImage.reset(new Image(imgSrc));//delete在reset内部被调用,没进入reset则不会调用,提供强烈保证
09 ++imageChanges;
10 }
美中不足的imgSrc:如果Image构造函数抛出异常,有可能输入流input stream的读取记号read marker(如读取指针)已被移动,而这样的搬移对程序其余部分是一种可见的状态改变。再读imgSrc就从先前的后面开始读取。
所以changeBackground在解决这个问题前只提供基本的异常安全保证。
强烈保证另一种方案: copy and swap 创建副本,对副本操作,若有异常,原始对象不变,若成功再在不抛出异常的swap中交换
01 //pimpl idiom手法:将隶属于对象的数据从原对象放进另一对象内,然后赋予原对象一个指针指向那个所谓的实现对象。条款31
02 struct PMImpl {
03 std::tr1::shared_ptr<Image> bgImage;
04 int imageChanges;
05 };
06 class PrettyMenu {
07 private:
08 Mutex mutex;
09 std::tr1::shared_ptr<pmimpl> pImpl;
10 };
11 void PrettyMenu::changeBackground(std::istream& imgSrc) {
12 using std::swap; // see Item 25
13 Lock ml(&mutex); // acquire the mutex
14 std::tr1::shared_ptr<pmimpl> // copy obj. data
15 pNew(new PMImpl(*pImpl));
16 pNew->bgImage.reset(new Image(imgSrc)); // modify the copy
17 ++pNew->imageChanges;
18 swap(pImpl, pNew); // swap the new
19 } // release the mutex
20 </pmimpl></pmimpl>
copy and swap 不保证整个函数有强烈的异常安全。
1 void someFunc() {
2 ... //对local状态做一份副本
3 f1();
4 f2();
5 ... //将修改后的状态置换回来
6 }
如果f1或f2异常安全性比强烈保证低,则someFunc很难获得强烈安全。
如果f1或f2都是强烈异常安全的,情况并不就此好转。如果f1圆满结束,程序状态有所改变。随后f2异常,程序状态和someFunc被调用前不会相同。
copy and swap的创建副本可能耗用无法供应的时间和空间,所以”强烈保证”并非在任何时刻都显得实际。所以有时候基本保证是个绝对通情达理的选择。
条款30:通彻了解inlining的里里外外
inline函数: 对此函数的每一个调用都以函数本体替换之。 因此可能增加目标码object code的大小,在一台内存有限的机器上,过度热衷inline会造成程序体积过大,即使拥有虚内存,inline造成的代码膨胀亦会导致额外的换页行为,降低指令高速缓存装置的击中率,以及伴随这些而立的效率损失。
而如果inline函数本体很小,编译器针对函数本体所产出的码可能针对函数调用所产出的码更小,可导致较小的目标码和较高的指令高速缓存装置击中率。
inline只是对编译器的一个申请,不是强制命令。
隐式声明inline: 将函数定义于class定义式内。 显示声明: 在函数定义式前加inline。
inline在大多数C++程序中是编译期行为。
如果你在写一个template而你认为所有根据次template具现出来的函数都应该为inline,则将此template声明为inline。否则不声明。
大多数编译期拒绝太过复杂(循环或递归)的函数inlining,而所有virtual函数调用(除非是最平淡无奇的)也都会使inlining落空(运行期行为)。
如果程序要取某个inline函数的地址或指针调用函数,编译器通常不对该函数实施inlining。
构造函数和析构函数往往是inlining的糟糕候选人。
当你使用new时,动态创建的对象被其构造函数自动初始化;当你使用delete时,析构函数被调用;
当你创建一个对象,其每一个base class及每一个成员变量都会被自动构造;当你销毁一个对象,反向程序的析构行为会自动发生;
如果有个异常在对象构造期间被抛出,该对象已构造好的那一部分会被自动销毁。
01 Derived::Derived(){ //空白Derived构造函数的观念性实现
02 Base::Base(); // initialize Base part
03 try { dm1.std::string::string(); } // try to construct dm1
04 catch (...) { // if it throws,
05 Base::~Base(); // destroy base class part and
06 throw; // propagate the exception
07 }
08 try { dm2.std::string::string(); } // try to construct dm2
09 catch(...) { // if it throws,
10 dm1.std::string::~string(); // destroy dm1,
11 Base::~Base(); // destroy base class part, and
12 throw; // propagate the exception
13 }
14 try { dm3.std::string::string(); } // construct dm3
15 catch(...) { // if it throws,
16 dm2.std::string::~string(); // destroy dm2,
17 dm1.std::string::~string(); // destroy dm1,
18 Base::~Base(); // destroy base class part, and
19 throw; // propagate the exception
20 }
21 }
inline函数改变后,所有用到该函数的程序都要重新编译,而non-inline函数则只需重新连接就好。
大部分调试器对inline函数调试束手无策。
条款31: 将文件间的编译依存关系降至最低。
Person的文件和它的头文件之间建立了编译依赖关系。如果任一个辅助类(即string, Date,Address和Country)改变了它的实现,或任一个辅助类所依赖的类改变了实现,包含Person类的文件以及任何使用了Person类的文件就必须重新编译。
解决方法: 用前置声明替换#include头文件, 声明的类型只能被使用为引用或指针,或在函数的声明中。
01 //方案一: handle class,pimpl idiom
02 //person.h
03 #ifndef PERSON_H
04 #define PERSON_H
05 #include <string> // 不能前置声明,因为std::string是个basic_string<char>类型的typedef
06 #include <memory> // for tr1::shared_ptr; see below
07 class PersonImpl; // 前置声明
08 class Date; // 前置声明
09 class Address; // 前置声明
10 class Person {
11 public:
12 Person(const std::string& name, const Date& birthday,
13 const Address& addr);
14 std::string name() const;
15 std::string birthDate() const;
16 std::string address() const;
17 private: // ptr to implementation;
18 std::tr1::shared_ptr<personimpl> pImpl; // see Item 13 for info on
19 }; // std::tr1::shared_ptr
20 #endif
21
22 //person.cpp
23 #include "Person.h" // we're implementing the Person class,
24 #include "PersonImpl.h" // we must also #include PersonImpl's class
25 Person::Person(const std::string& name, const Date& birthday,
26 const Address& addr)
27 : pImpl(new PersonImpl(name, birthday, addr)) {
28 }
29 std::string Person::name() const {
30 return pImpl->name();
31 }
32 std::string Person::birthDate() const {
33 return pImpl->birthDate();
34 }
35 //PersonImpl.h
36 #ifndef PERSONIMPL_H
37 #define PERSONIMPL_H
38 #include "Date.h"
39 #include "Address.h"
40 class PersonImpl {
41 public:
42 PersonImpl(const std::string& name, const Date& birthday,
43 const Address& addr):theName(name),theBirthDate(birthday),theAddress(addr){}
44 std::string name() const { return theName; }
45 std::string birthDate()const { return theBirthDate.toString();}
46 private:
47 std::string theName; // implementation detail
48 Date theBirthDate; // implementation detail
49 Address theAddress; // implementation detail
50 };
51 #endif
52
53 //Date.h
54 #ifndef DATE_H
55 #define DATE_H
56 class Date {
57 public:
58 Date(int m, int d, int y):month(m),day(d),year(y){
59 }
60 std::string toString() const{ char buff[20];sprintf(buff,"today is %d %d %d",month,day,year);return buff;}
61 private:
62 int month;
63 int day;
64 int year;
65 };
66 #endif
67 </personimpl></memory></char></string>
设想下如果为Date.h被修改,包含它的PersonImpl.h就需要重新编译,person.cpp要重新编译,而person.h不变,所以外部程序包含person.h不需要重新编译。
而如果PersonImpl增加删减一个成员变量,构造函数就要改变,person.cpp就需要重新编译,如果因此person.h的构造函数及因增减的成员相关的成员函数变化了,即person本身的接口变了,那么包含person.h的所有文件都要重新编译。但是需要使用Person类的类也可以在改类头文件中使用前置声明, 然后只需重新编译该类,而包含该类头文件的其他类就不需要重新编译了。
所以本条款就是把编译相关的文件局部于一定的的文件层次中。
另外可以为声明式和定义式提供不同的头文件: 文件需要保持一致性,如<iosfwd>包含iostream的各组件的声明式,其定义则分布在<sstream><streambuf><fstream><iostream>
01 //方案二: Interface class
02 //Person.h
03 #ifndef PERSON_H
04 #define PERSON_H
05 #include <string> // standard library components
06 // shouldn't be forward-declared
07 #include <memory> // for tr1::shared_ptr; see below
08
09 class Date; // forward decls of classes used in
10 class Address; // Person interface
11 class Person {
12 public:
13 static std::tr1::shared_ptr<person> // return a tr1::shared_ptr to a new
14 create(const std::string& name, // Person initialized with the
15 const Date& birthday); // why a tr1::shared_ptr is returned
16 virtual std::string name() const = 0;
17 virtual std::string birthDate() const = 0;
18 };
19 #endif
20
21 //Person.cpp
22 #include "Person.h"
23 #include "RealPerson.h"
24
25 std::tr1::shared_ptr<person>
26 Person::create(const std::string& name,
27 const Date& birthday){
28 return std::tr1::shared_ptr<person>(new RealPerson(name, birthday));
29 }
30
31 //RealPerson.h
32 #ifndef REALPERSON_H
33 #define REALPERSON_H
34 #include "Date.h"
35 class Address;
36 class RealPerson: public Person {
37 public:
38 RealPerson(const std::string& name, const Date& birthday)
39 : theName(name), theBirthDate(birthday)
40 {}
41 virtual ~RealPerson() {}
42 std::string name() const {return theName;}
43 std::string birthDate() const{return theBirthDate.toString();}
44 private:
45 std::string theName;
46 Date theBirthDate;
47 };
48 #endif
49 </person></person></person></memory></string>
因为Person是抽象类不能创建对象实例,而在现实中,Person::create可能会创建不同的类型的derived class对象,取决于额外参数值, 读自文件或数据库的数据,环境变量等。
而无论handle class还是 interface class 都必然会使你在运行期付出额外的空间和时间代价。
条款32: 确定你的public 继承塑膜出is-a关系
public继承意味is-a, 适用于base classes身上的每一件事一定也适用于derived classes,因为每一个derived classes也都是一个base class对象。
01 class Bird {
02 public:
03 virtual void fly(); // birds can fly
04 };
05
06 class Penguin:public Bird { // penguins are birds
07 };// 企鹅会飞吗???
08
09 /*********************************************************/
10 class Bird {
11
12 ... // no fly function is declared
13
14 };
15 class FlyingBird: public Bird {
16 public:
17 virtual void fly();
18 };
19 class Penguin: public Bird {
20
21 ... // no fly function is declared
22 };
23
24 /*********************************************************/
25 void error(const std::string& msg); // defined elsewhere
26 class Penguin: public Bird {
27 public:
28 virtual void fly() { error("Attempt to make a penguin fly!");}
29 }; //不是说企鹅不会飞,而其实是在说企鹅会飞,但尝试那么做是一种错误。。
当然如果设计的类无关乎会不会飞,就最好了。。。
01 class Rectangle {
02 public:
03 virtual void setHeight(int newHeight);
04 virtual void setWidth(int newWidth);
05 virtual int height() const; // return current values
06 virtual int width() const;
07 };
08 void makeBigger(Rectangle& r) //增加矩形面积,高不变,宽增加
09 {
10 int oldHeight = r.height();
11 r.setWidth(r.width() + 10);
12 assert(r.height() == oldHeight);
13 }
14 class Square: public Rectangle {...};
15 Square s;
16 assert(s.width() == s.height()); // this must be true for all squares
17 makeBigger(s); //正方形能高不变只增加宽吗,这样还是正方形吗????
18 assert(s.width() == s.height()); // this must still be true for all squares
条款33: 避免遮掩继承而来的名称
derived classes内的名称会遮掩base classes内的名称。在public继承下从来没人希望如此
为了让被遮掩的名称再见天日,可使用using声明式或转交函数
01 class Base {
02 private:
03 int x;
04 public:
05 virtual void mf1() = 0;
06 virtual void mf1(int);
07 virtual void mf2();
08 void mf3();
09 void mf3(double);
10 };
11 class Derived: public Base {
12 public:
13 virtual void mf1();
14 void mf3();
15 void mf4();
16 };
17 Derived d;
18 int x;
19 d.mf1(); // fine, calls Derived::mf1
20 d.mf1(x); // error! Derived::mf1 hides Base::mf1
21 d.mf2(); // fine, calls Base::mf2
22 d.mf3(); // fine, calls Derived::mf3
23 d.mf3(x); // error! Derived::mf3 hides Base::mf3
24 //遮掩名称,和类型无关
25
26 void Derived::mf4(){
27 mf2();}
28 //查找顺序local->Derived->Base->Base 的namespace-> global
可以在Derived class内用using声明被遮掩的Base class名称。
1 class Derived: public Base {
2 public:
3 using Base::mf1;// 让Base class内名为mf1的所有东西都在Derived作用域内可见
4 virtual void mf1();
5 void mf3();
6 void mf4();
7 };
如果不想继承base class所有函数,只让部分函数可见
转交函数forwaring function
01 class Base {
02 public:
03 virtual void mf1() = 0;
04 virtual void mf1(int);
05 };
06 class Derived: private Base { //因为public继承意味is-a关系,不能遮掩Base
07 public:
08 virtual void mf1() { Base::mf1(); } //转交函数, 隐式inline
09 };
10 Derived d;
11 int x;
12 d.mf1(); // fine, calls Derived::mf1
13 d.mf1(x); // error! Base::mf1() is hidden
条款34: 区分接口继承和实现继承
成员函数的接口总是会被继承。public继承意味is-a,所以某个函数可以用于base class身上,也一定可以用于它的derived classes。
声明一个pure virtual函数的目的是为了让derived classes只继承函数接口
声明impure virtual(非纯虚)函数的目的,是为了让derived classes继承该函数的接口和缺省实现。
声明non-virtual函数的目的是为了让derived classes继承函数的接口及一份强制性实现。
01 //关于impure virtual的缺省实现
02 class Airport { ... };
03 class Airplane {
04 public:
05 virtual void fly(const Airport& destination);
06 };
07 void Airplane::fly(const Airport& destination) {
08 缺省代码,将飞机飞往指定的目的地
09 }
10 class ModelA: public Airplane { ... }; ///继承Airplane::fly缺省行为
11 class ModelB: public Airplane { ... };///继承Airplane::fly缺省行为
12 class ModelC: public Airplane {
13 //未声明fly函数
14 };
15 Airport PDX(...);
16 Airplane *pa = new ModelC;
17 pa->fly(PDX); // calls Airplane::fly!
切断vitural函数接口和其缺省实现的连接
01 class Airplane {
02 public:
03 virtual void fly(const Airport& destination) = 0; //声明为纯虚函数
04 protected:
05 void defaultFly(const Airport& destination);
06 };
07 void Airplane::defaultFly(const Airport& destination) {
08 缺省行为,将飞机飞至指定的目的地
09 }
10 class ModelA: public Airplane {
11 public:
12 virtual void fly(const Airport& destination){
13 defaultFly(destination); }
14 };
15 class ModelB: public Airplane {
16 public:
17 virtual void fly(const Airport& destination){
18 defaultFly(destination); }
19 };
20 class ModelC: public Airplane {
21 public:
22 virtual void fly(const Airport& destination); //Airplane中的纯虚函数迫使必须提供自己的fly版本
23 };
24 void ModelC::fly(const Airport& destination) {
25 将C型飞机飞至指定的目的地
26
27 }
有人担心fly和defaultFly过度雷同的名称而引起的class命名空间污染问题。。
01 class Airplane {
02 public:
03 virtual void fly(const Airport& destination) = 0;//声明为纯虚函数,让derived classes必须重写
04 };
05 void Airplane::fly(const Airport& destination) { //提供缺省行为,只能被derived classes显示调用
06 缺省行为 //而原先defaultFly是protected,现在为public
07 }
08 class ModelA: public Airplane {
09 public:
10 virtual void fly(const Airport& destination) {
11 Airplane::fly(destination); }
12 };
13 class ModelB: public Airplane {
14 public:
15 virtual void fly(const Airport& destination){
16 Airplane::fly(destination); }
17 };
18 class ModelC: public Airplane {
19 public:
20 virtual void fly(const Airport& destination);
21 };
22 void ModelC::fly(const Airport& destination) {
23 自定义行为
24 }
non-virtual函数: 不变性invariant凌驾其特异性specialization
条款35: 考虑virtual函数以外的其他选择
借由Non-Virtual Interface手法实现Template Method设计模式
01 //NVI手法:增加一个外层函数,里面调用虚函数,可增加事前事后处理工作。其实还是使用virtual
02 class GameCharacter {
03 public:
04 int healthValue() const { //增加一层外覆器wrapper
05 ... //做一些事前工作,设定场景
06 int retVal = doHealthValue(); // do the real work
07 ... //事后清理
08 return retVal;
09 }
10 private:
11 virtual int doHealthValue() const {
12 ...
13 }
14 };
NVI手法其实没有必要让virtual一定是private,可以为protected(如derived class调用base class里同名函数),可以为public(如virtual析构函数)
借由Function Pointers 实现Strategy设计模式:
优点:每个对象可以有自己的健康计算函数和可在运行期改变计算函数; 缺点:可能降低封装性 (不能访问non-public成员),需要声明friend或提供public访问接口
01 class GameCharacter; // forward declaration
02 int defaultHealthCalc(const GameCharacter& gc);
03 class GameCharacter {
04 public:
05 typedef int (*HealthCalcFunc)(const GameCharacter&);
06 explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
07 :healthFunc(hcf){}
08 int healthValue() const { return healthFunc(*this); }
09 private:
10 HealthCalcFunc healthFunc;
11 };
12 class EvilBadGuy: public GameCharacter { //同一人物的不同实体可以有不同计算函数,比起virtual函数
13 public:
14 explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)
15 :GameCharacter(hcf) {
16 ... }
17 };
18 int loseHealthQuickly(const GameCharacter&); // health calculation
19 int loseHealthSlowly(const GameCharacter&); // funcs with different
20
21 EvilBadGuy ebg1(loseHealthQuickly);
22 EvilBadGuy ebg2(loseHealthSlowly);
借由tr1::function完成Strategy设计模式
01 class GameCharacter; // as before
02 int defaultHealthCalc(const GameCharacter& gc); // as before
03 class GameCharacter {
04 public:
05 //HealthCalcFunc为任何可调用物,如函数指针、函数对象;接受可隐式转为const GameCharacter&的形参,返回兼容int的类型
06 typedef std::tr1::function<int (const="" gamecharacter&)=""> HealthCalcFunc;
07 explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
08 : healthFunc(hcf){}
09 int healthValue() const { return healthFunc(*this);}
10 private:
11 HealthCalcFunc healthFunc;
12 };
13 short calcHealth(const GameCharacter&);
14 struct HealthCalculator {
15 int operator()(const GameCharacter&) const
16 { ... }
17 };
18 class GameLevel {
19 public:
20 float health(const GameCharacter&) const;
21 };
22 class EvilBadGuy: public GameCharacter {
23 ...
24 };
25 class EyeCandyCharacter: public GameCharacter {
26 ...
27 };
28
29 EvilBadGuy ebg1(calcHealth);
30 EyeCandyCharacter ecc1(HealthCalculator()); //使用函数对象
31 GameLevel currentLevel;
32 EvilBadGuy ebg2( //使用某个类的成员函数
33 std::tr1::bind(&GameLevel::health,
34 currentLevel,
35 _1)
36 );
37 //GameLevel::health实际上有两个形参,一个为隐含this指针,-1为占位符,为bind返回的函数对象调用时候的第几个形参
38 </int>
古典的Strategy设计模式 : 将计算函数做成一个分离的继承体系中的virtual成员函数
01 class GameCharacter;
02 class HealthCalcFunc {
03 public:
04 virtual int calc(const GameCharacter& gc) const
05 { ... }
06 };
07 HealthCalcFunc defaultHealthCalc;
08 class GameCharacter {
09 public:
10 explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
11 : pHealthCalc(phcf)
12 {}
13 int healthValue() const
14 { return pHealthCalc->calc(*this);}
15 private:
16 HealthCalcFunc *pHealthCalc;
17 };
18 //可为HealthCalcFunc继承体系添加derived class来增加健康计算方法
条款36: 绝不重新定义继承而来的non-virtual函数 : public 继承is-a关系,non-virtual不变性凌驾特异性条款34
条款37: 绝不重新定义继承而来的缺省参数值
01 //virtual是动态绑定,默认参数是静态绑定,所以看静态类型
02 class Shape {
03 public:
04 enum ShapeColor { Red, Green, Blue };
05 virtual void draw(ShapeColor color = Red) const = 0;
06 };
07 class Rectangle: public Shape {
08 public:
09 virtual void draw(ShapeColor color = Green) const;
10 };
11 class Circle: public Shape {
12 public:
13 virtual void draw(ShapeColor color) const; //动态绑定下才会从base继承缺省参数值
14 };
15
16 Shape *pr = new Rectangle; // static type = Shape*
17 pr->draw(); // calls Rectangle::draw(Shape::Red)!!!!
解决方案: NVI手法,条款35
01 class Shape {
02 public:
03 enum ShapeColor { Red, Green, Blue };
04 void draw(ShapeColor color = Red) const {
05 doDraw(color);
06 }
07 private:
08 virtual void doDraw(ShapeColor color) const = 0; // the actual work is
09 }; // done in this func
10
11 class Rectangle: public Shape {
12 public:
13 private:
14 virtual void doDraw(ShapeColor color) const; //无须指定缺省参数值
15 };
条款38: 通过复合塑模出has-a或“根据某物实现出”。Model “has-a” or ”is-implemented-in-terms-of”through composition.
当复合(composition)发生于应用域内的对象之间,表现出has-a关系;当发生于实现域内则是表现出is-impemented-in-terms-of关系。
复合即为包含。应用域:人、汽车等实物。 实现域:缓冲区、互斥器、查找树等等。
条款39: 明智而审慎地使用private继承。
private继承意味is-implemented-in-terms-of.它通常比复合的级别低. 但是当derived class需要访问protected base class成员,或需要重新定义继承而来的virtual函数时,才合理。
和复合不同,private继承可以造成empty base最优化
01 class Person { ... };
02 class Student: private Person { ... }; // inheritance is now private
03 void eat(const Person& p); // anyone can eat
04 void study(const Student& s); // only students study
05
06 Person p; // p is a Person
07 Student s; // s is a Student
08 eat(p); // fine, p is a Person
09 eat(s); // error! a Student isn't a Person
10 //private继承,编译器不会自动将一个derived class 对象转换为一个base class对象
private继承意味is-implemented-in-terms-of, 与条款38的复合在应用域的表现一样。
尽可能使用复合, 必要时才使用private继承。合适才是必要?当protected成员或virtual函数牵扯进来时。还有一种激进情况,当空间方面的厉害关系足以踢翻private继承的支柱时。
设计一个Widget类,通过复用Timer类来周期性审查每个成员函数的被调用次数。并重写Timer的虚函数。
01 class Timer {
02 public:
03 explicit Timer(int tickFrequency);
04 virtual void onTick() const; // automatically called for each tick
05 ...
06 };
07 //为了让Widget重新定义Timer的virtual,必须继承Timer
08 //但public继承在此并不适用,is-a关系,而且Widget调用OnTick容易造成接口误用,违反条款18
09
10 //所以private继承
11 class Widget: private Timer {
12 private:
13 virtual void onTick() const; // look at Widget usage data, etc.
14 ...
15 };
16
17 //但private继承绝非必要,可以使用public继承加复合
18 class Widget {
19 private:
20 class WidgetTimer: public Timer {
21 public:
22 virtual void onTick() const;
23 ...
24 };
25 WidgetTimer timer;
26 ...
27 };
为什么愿意选择public继承加复合而非private继承?
1)如果你想从Widget派生出其他类,而同时想阻止其derived class重新定义OnTick,但无论什么继承派生类都可以重写虚函数。
但WidgetTimer是private成员,Widget的derived class将无法取用WidgetTimer,因此无法继承它或重新定义它的virtual函数
2)你可能会想要将Widget的编译依存性降至最低。如果Widget继承Timer,当Widget编译时,Timer定义必须可见。
如果WidgetTimer移到Widget之外而Widget内含指针指向一个WidgetTimer,则不需要include。
激进情况: 如果你的class不到任何数据,没有non-static成员变量,没有virtual函数(会有vptr),没有virtual base class。而C++裁定凡是独立对象都必须有非零大小。
01 class Empty {};
02 class HoldsAnInt {
03 private:
04 int x;
05 Empty e;
06 };
07 // sizeof(HoldsAnInt) > sizeof(int);
08 //C++官方勒令安插一个char到空对象。然而齐位需求alignment,可能被扩充到int
09
10 class HoldsAnInt: private Empty {
11 private:
12 int x;
13 };
14 //sizeof(HoldsAnInt) == sizeof(int)
15 //EBO(empty base optimization)空白基类最优化,EBO一般只在单一继承下才可行
条款40: 明智而审慎地使用多重继承。
多重继承比单一继承复杂。可能导致新的歧义性,以及对virtual继承的需要
virtual继承会增加大小、速度、初始化(赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况。
多重继承的确有正当用途。public继承某个Interface class,private继承某个协助实现的class
1 //多重继承,virtual base class
2 class File { ... };
3 class InputFile: virtual public File { ... };
4 class OutputFile: virtual public File { ... };
5 class IOFile: public InputFile,
6 public OutputFile
7 { ... };
virtual继承的classes所产生对象往往体积更大,访问virtual base classes 的成员变量时速度更慢。
而且virtual bsae class的初始化责任是由继承体系中的最底层class负责的。
因此:1)非必要不要使用virtual继承 2)如必须使用virtual base classes,尽可能避免在其中放置数据。
现在要以IPerson的指针和引用来编写程序,并用factory function创建对象。
现在要写个CPerson类,继承并重写IPerson的pure virtual函数的实现。
我们可以自己写这个实现,但我们已经找到一个PersonInfo可以提供CPerson所需要的实现内容。
is-implemented-in-terms有两种方式,复用和private继承,但本例CPerson要重新定义valueDelimOpen和valueDelimClose,所以这里用private继承,当然也可以复合+继承。
01 //public继承某接口,private继承自某实现
02
03 class IPerson {
04 public:
05 virtual ~IPerson();
06 virtual std::string name() const = 0;
07 virtual std::string birthDate() const = 0;
08 };
09 class DatabaseID { ... };
10 class PersonInfo {
11 public:
12 explicit PersonInfo(DatabaseID pid);
13 virtual ~PersonInfo();
14 virtual const char * theName() const;
15 virtual const char * theBirthDate() const;
16 virtual const char * valueDelimOpen() const;
17 virtual const char * valueDelimClose() const;
18 };
19
20 class CPerson: public IPerson, private PersonInfo { // note use of MI
21 public:
22 explicit CPerson( DatabaseID pid): PersonInfo(pid) {}
23 virtual std::string name() const // implementations
24 { return PersonInfo::theName(); } // of the required IPerson member
25 virtual std::string birthDate() const // functions
26 { return PersonInfo::theBirthDate(); }
27 private: // redefinitions of
28 const char * valueDelimOpen() const { return ""; } // inherited virtual
29 const char * valueDelimClose() const { return ""; } // delimiter
30 }; // functions
【转载】http://www.cnblogs.com/Atela/archive/2011/04/14/2016420.html