注:本笔记只适用于本人,想怎么写就怎么写,想写多少就写多少,想略过就略过,完全随心所欲。
条款07:多态基类声明virtual析构函数。
C++明白指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义-----实际执行时通常发生的是对象的derived成分没被销毁。(说说感想,这条规定是让我觉得不可理喻,很傻逼,即使是一个base class指针它在执行期时也有足够的信息获取它所指向的实际derived class,为什么不直接把derived class部分也处理了呢?)
而且,就算base class中没有带任何virtual函数,直接删除一个指向dervied class实例的base class指针,这样的结果也是没有定义的。也就是说你不能直接继承string,stl等类。因为它们不是用来继承的。
条款08:别让异常逃离析构函数。
这个人人都知道
条款09:绝不在构造和析构过程中调用virtual函数。
这个我知道,因为我看了《C++对象模型》
条款10:令operator=返回一个reference to *this。
(a = b) = c;
为了支持这种赋值。
条款11:operator=中不要自我赋值
Widget&
Widget::operator=(const Widget& rhs) {
delete pb; // 这里会傻逼
pb = new Bitmap(*rhs.pb);
return *this;
}
条款13:以对象管理资源
也就是RAII(资源获取就是初始化)
条款18:让接口容易被正确使用,不易被误用
class Date {
public:
Date(int year, int month, int day) {}
}
不如下面的安全
struct Day {
explicit Day(int d) {}
};
struct Month {
explicit Month(int m) {}
};
struct Year {
explicit Year(int y) {}
};
class Date {
public:
Date(const Year& y, const Month& m, const Day& d) {}
}
条款21:必须返回对象时,别返回引用
返回引用不安全,并且有的时候返回对象,编译器会进行named return value优化,效率会很高哦~
条款22:成员变量声明为private
封装啊,蠢货。如果你通过函数访问成员变量,日后可改以某个计算替换这个成员变量,而class客户一点也不知道class的内部实现已经起了变化。
在封装的眼里只有private和其他限定访问符(protected跟public没区别)。
条款23:宁以non-member,non-friend替换member函数
面向对象守则要求,数据以及操作数据的那些函数应该被绑在一块,这意味着它建议member函数是较好的选择。不幸的是这个建议不正确。这是基于对面向对象真实意义的一个误解。面向对象守则要求数据应该尽可能被封装,然而与直观相反的,member函数clearEverything带来的封装性比non-member函数clearBrowser低。并且non-member函数有较大的包裹弹性。封装是为了让更少的人去看到它,这样我们就能够改变事物而只影响有限客户。
条款24:若所有参数皆需类型转换,请为此采用non-member函数
class Rational {
public:
...
const Rational operator*(const Retional&rhs) const {}
};
result = oneHalf * 2; // ok
result = 2 * oneHalf; // error
把它转换成non-member是不是更好?
const Rational operator*(const Rational& lhs, const Rational& rhs) {
....
}
条款25:写出一个不抛异常的swap函数条款26:尽可能延后变量定义式的出现时间
注意注意。
条款27:尽量少做转型动作
class Window {
public:
virtual void onResize() {}
};
class SpecialWindow:public Window {
public:
virtual void onResize() {
static_cast<Window>(*this).onResize(); // 这跟Window::onResize()不是一回事,static_cast<Window>(*this)会产生一个副本,执行的是这个副本的onResize
...
}
};
dynamic_cast效率低,要求效率的程序要少用
条款29:为“异常安全”而努力是值得的
条款30:透彻了解inlining的里里外外
inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出。把一个函数定义在class定义式内,就是隐喻提出。
Inlining在大多数C++程序中时编译期行为。
Template的具现化与inlining无关。
大部分编译器将太过复杂的函数inlining,而所有对virtual函数的调用也会使inlining落空。
条款31:将文件间的编译依存关系降至最低
在C++中,编译器看到Person p;p的定义就必须知道要分配多少空间放置一个Person。这个问题在java上不存在,因为定义一个java对象时,编译器只分配足够空间给一个指针。
class Person {
public:
Person(const std::string& name, const Date& birthday, const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
这样的定义式通常由#include指示符提供,所以Person定义文件的最上方很可能是这样的东西
#include <string>
#include "date.h"
#include "address.h"
这样就形成一层依赖关系。如果这些头文件有任何改变,那么每一个含入Person class的文件就得重新编译。
只对Person我们可以这样做:把Person分割为2个classes,一个只提供接口,另一个负责实现该接口。
#include <string>
#include <memory>
class PersonImpl;
class Date;
class Address;
class Person {
public:
Person(const std::string& name, const Date& birthday, const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
private:
std::tr1::shared_ptr<PersonImpl> pImpl;
};
这样的设计之下,Person的客户就完全与Dates, Addresses以及Persons的实现细目分离了。那些classes的任何实现修改都不需要Person客户端的从小编译。这真正是“
接口与实现分离”。
如果使用object references或object pointers可以完成任务,就不要使用objects。
如果能够,尽量以class声明式替换class定义式。
class Date; // class声明式
Date today(); // 没问题,这里并不需要
void clearAppointments(Date d);// Date的定义式
为声明式和定义式提供不同的头文件。
请记住:
支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。
程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。
条款34:区分接口继承和实现继承
class Airport {...};
class Airplane {
virtual void fly(const Airport& destination);
};
void Airplane::fly(const Airport& destination) {
}
class ModelA : public Airplane {...}
class ModelB : public Airplane {...}
如果此时又有一个ModelC,但是ModelC的fly默认的目的地并不是Airplane中定义的fly。改进:
class Airport {...};
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
protected:
void defaultFly(const Airport& destination);
};
void Airplane::defaultFly(const Airport& destination) {
// 缺省行为
}
class ModelA : public Airplane {
public:
virtual void fly(const Airport& destination) {
defaultFly(destination);
}
}
class ModelB : public Airplane {
public:
virtual void fly(const Airport& destination) {
defaultFly(destination);
}
}
class ModelC : public Airplane {
public:
virtual void fly(const Airport& destination) {
.....
}
}
看起来没有问题,可是程序员还是可能因为剪贴代码而招来麻烦。
class Airport {...};
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
};
void Airplane::fly(const Airport& destination) {
// 缺省行为
}
class ModelA : public Airplane {
public:
virtual void fly(const Airport& destination) {
Airplane::fly(destination);
}
}
class ModelB : public Airplane {
public:
virtual void fly(const Airport& destination) {
Airplane::fly(destination);
}
}
class ModelC : public Airplane {
public:
virtual void fly(const Airport& destination) {
.....
}
}
这个几乎和前一个设计一模一样,只不过pure virtual 函数Airplane::fly替换了独立函数Airplane::defaultFly。本质上,现在的fly被分割为两个基本要素:其声明部分表现的是接口,其定义部分则表现出缺省行为。
条款35:考虑virtual函数以外的其他选择
藉由Non-Virtual Interface手法实现Template Method模式(与C++ templates并无关联):
class Base {
public:
int healValue() {
... // do something
doHealValue();
... // do something
}
private:
virtual void doHealValue() {
cout << "Base::doHealValue" << endl;
}
};
NVI手法的一个优点就是可以隐身在“做一些事前工作”和“做一些事后工作”之中。
藉由Function Pointers实现Strategy模式:
class GameCharactor;
int defaultHealthCalc(const GameCharactor& gc);
class GameCharacter {
public:
typedef int (*HealthCalcFunc)(const GameCharacter&);
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf){}
int healthValue() const {
return healthFunc(*this);
}
private:
HealthCalcFunc healthFunc;
};
这样会有一个问题,就是non-memeber函数如何访问class的non-public成分。唯一能够解决它的办法就是:弱化class的封装。例如class可声明那个non-member函数为friends。
藉由tr1::function完成Strategy模式:
一如之前
class GameCharactor;
int defaultHealthCalc(const GameCharactor& gc);
class GameCharacter {
public:
// HealthCalcFunc可以是任何“可调用物”,可被调用并接受任何兼容于GameCharacter之物,返回任何兼容于int的东西。
typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf){}
int healthValue() const {
return healthFunc(*this);
}
private:
HealthCalcFunc healthFunc;
};
我们看看HealthCalcFunc是个什么样的typedef:
std::tr1::function<int (const GameCharacter&)>
这个签名代表的函数是“接受一个reference指向const GameCharacter,并返回int”。和函数指针相比,这个设计几乎相同。唯一不同的是如今GameCharactor持有一个tr1::function对象,相当于一个指向函数的泛化指针。弹性很大:
short calcHealth(const GameCharacter&); // 注意其返回值
struct HealthCalculator {// 函数对象
int operator()(const GameCharacter&) const {}
};
class GameLevel {
public:
float health(const GameChacter&) const; // 成员函数
};
class EvilBadGuy: public GameCharacter { // 同前
};
class EyeCandyCharacter : public GameCharacter {
}
EvilBadGuy ebg1(calcHealth); // 人物1,使用某个函数计算
EyeCandyCharacter ecc1(HealthCalculator()); // 人物2,使用某个函数对象计算
GameLevel currentLevel;
EvilBadGuy ebg2 ( // 人物3,使用某个成员函数计算
std::tr1::bind(&GameLevel::health, currentLevel, _1)
);
古典的Strategy模式,GameCharacter中内含一个指向HealthCalcFunc(健康计算函数对象)。
条款36:绝不重新定义继承而来的non-virtual函数
条款37:绝不重新定义继承而来的缺省参数值
因为缺省参数值都是静态绑定,而virtual函数----你唯一应该覆写的东西----却是动态绑定的。
什么是静态类型呢?
class Shape {
public:
enum ShapeColor {Red, Green, Blue};
// 所有形状都必须提供一个函数,用来绘出自己
virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle:public Shape {
public:
// 注意,赋予不同的缺省参数值。这真糟糕
virtual void draw(ShapeColor color = Green) const;
};
class Circle : public Shape {
public:
virtual void draw(ShapeColor color) const;
// 请注意,以上这么写则当客户以对象调用此函数,一定要指定参数值。
// 因为静态绑定下这个函数并不从其base继承缺省参数值。
// 但若以指针(或reference)调用此函数,可以不指定参数值,
// 因为动态绑定下这个函数会从其base继承缺省参数值。
};
Shape *ps; // 静态类型为Shape*
Shape *pc = new Circle; // 静态类型为Shape*
Shape *pr = new Rectangle; // 静态类型为Shape*
pr->draw(); // 调用Rectangle::draw(Shape::Red)!而不是Shape::Green
因为虽然pr的动态类型是Rectangle*,所以调用的是Rectangle的virtual函数。Rectangle::draw函数的缺省参数值应该是GREEN,但由于pr的静态类型是Shape*,所以此一调用的缺省参数值来自Shape class而非Rectangle class!
之所以这样做,是为了效率。
解决这个的办法是使用NVI,就是non-virtual interface。见条款36
条款39:明智而审慎地使用private继承
class Person {...};
class Student:private Person {...};
void eat(const Person& p);
void study(const Student& s);
Person p;
Student s;
eat(p);
eat(s); // error
条款40:明智而审慎地使用多重继承
慎用。如果你唯一能够提出的设计方案设计多重继承,你应该更努力想一想--------几乎可以说一定会有某些方案让单一继承行得通。然而多重继承有时候的确是完成任务之最简洁,最易维护,最合理的做法,那就别怕它。
模板与泛型编程
条款41:了解隐式接口和编译期多态
面向对象编程世界总是以显示接口和运行期多态来解决问题。
Templates及泛型编程的世界,与面向对象有根本上的不同。在此世界中显示接口和运行期多态仍然存在,但重要性降低。反倒是隐式接口和编译期多态更为重要。
显示接口由函数的签名式(也就是函数名称,参数类型,返回类型)构成。隐式接口并不基于函数签名式,而是由有效表达式组成。
条款42:了解typename的双重意义
声明template参数时,前缀关键字class和typename可互换
请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。
条款43:学习处理模板化基类内的名称
条款45:运用成员函数模板接收所有兼容类型
请使用member function templates生成“可接受所有兼容类型”的函数
如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”,你还需要声明正常的copy构造函数和copy assignment操作符。
template<class T>
class shared_ptr {
public:
shared_ptr(shared_ptr const& r); // copy 构造函数
template<class Y> // 泛化copy构造函数
shared_ptr(shared_ptr<Y> const& r);
shared_ptr& operator=(shared_ptr const& r); // copy assignment
template<class Y> // 泛化copy assignment
shared_ptr& operator=(shared_ptr<Y> const& r);
};
条款46:需要类型转换时请为模板定义非成员函数
见条款24。当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”
之所以要friend是因为如果你想访问一个template内的函数必须在其内部才可以使用。
条款48:认识template元编程
TMP有两个伟大的效力。第一,它让某些事情更容易。如果没有它,那些事情将是困难的,甚至不可能的。第二,由于template metaprograms执行于C++编译期,因此可将工作从运行期转移到编译期。不过编译时间变长了。
typeid
template<unsigned n>
struct Factorial {
enum {value = n * Factorial<n-1>::value};
};
template<>
struct Factorial<0> {
enum {value = 1};
};