条款13 抛出一个异常和函数调用之间的区别
- 在抛出异常时,异常对象总是会被复制,无论抛出的是一个指针、对象还是引用。抛出的是指针时,将复制这个指针的内容(仅仅是地址,而不是对象,类似于浅拷贝),注意千万不要抛出局部对象的指针,不然外部捕获的将是一个悬垂的对象;抛出引用时,将以引用的静态类型(而不是动态类型)为蓝本进行复制;抛出一个对象时,还是会复制一个该对象,如果在捕获函数对象的时候,使用值传递的方式,甚至会复制2次。
- 捕获异常的时候,允许的转换动作要少一些。只允许2种形式:1.异常继承体系中的类转换。2.有型指针转换为void*指针的转换。
- 多个catch语句时,匹配采用的是first fit策略。虚函数则采用的是best fit策略。
读者认为,这里要将抛出异常和捕获异常的过程,分开来看。
效率
条款16 80-20 法则
- 一个程序80%的时间花费在20%的代码上,80% 的内存被20%的代码使用。80% 的磁盘访问动作由20%的代码执行,80%的维护力气花费在20%的代码上。
- 程序性能特质倾向高度的非直觉性,应该借助程序分析器profiler来优化代码。
条款 17 缓式评估lazy evaluation
- 也就是拖延战术。直到万不得已的时候才真正做最要紧的事。
- 1.引用计数和数据共享
string s1 = "hello";
string s2 = s1; //这时s2没有自己的私有数据,而是共享s1的数据
cout << s1;
cout << s1 + s2; //这时因为是读操作,s2还是没有自己的私有数据,仍旧是共享s1的数据
s2.upper();//将s2的小写转为大写,编译器在此之前,才会将s2构造出私有数据,因为不得不构造一份数据了
- 2.区分读和写。运用缓式评估和proxy classes,可以区分效率成本不同的读和写。
string s = "hello world!";
cout << s[1]; //读操作
s[2] = 'd'; //写操作
- 3.缓式取出。当存取一个大型的对象时,而只使用某些字段时:我们构造一个外壳,只需要在真正使用这些字段时,才进行存取这些需要的字段。当然这会造成额外的代码负担。
- 4.表达式缓评估。
条款18 分摊计算时的成本到计算之前
此条款的含义是超急评估over eager evaluation, 尽早的提前做事。典型的以空间换取时间的做法。
- 1.缓存 caching。
- 2.预取出 prefetching。
条款19 了解临时对象的来源
函数中具有明确名字的对象叫局部对象,C++中真正的临时对象是不可见的,这些临时对象。
主要有2种情况发生:
- 当隐式类型转换发生的时候。
- 函数返回对象的时候。
任何时候,只要看到reference-to-const的函数参数,就十分有可能产生一个临时对象。任何时候当你看到函数返回对象的时候,也会产生临时对象。
条款20 协助完成“具名返回值优化NRVO” name return value optimization
- 函数需要返回对象的时候,不要返回内部对象的指针和引用。指针需要手动去释放内存,而内部对象的引用对象会在函数体结束被销毁。
- 最优效率的写法:(会触发具名返回值优化)
inline const Rational operator*(const Rational& lhs, const Rational & rhs){
return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
- 具名返回值优化需要这个类具有copy constructor 函数,因为本优化就是为了 消除copy constructor的函数调用的问题。
- 具名返回值优化的副作用是copy constructor函数的代码不会被执行。
条款21 利用重载技术避免隐式转换
隐式转换时候,很方便程序员写代码。但是隐式转换会导致产生一个临时对象,影响效率。
比较好的方法时,对于可能的隐式转换都定制重载函数。
class UPInt{
public:
UPInt();
UPInt(int);
};
const UPInt operator+(const UPInt& lhs, const UPInt& rhs);
UPInt upi1, upi2;
UPInt upi3 = upi1 + upi2;
upi3 = 1 + upi1;//这里会进行隐式转换
upi3 = upi2 + 1;//这里会进行隐式转换
修改如下:
const UPInt operator+(const UPInt& lhs, int rhs);
const UPInt operator+(int lhs, const UPInt& rhs);
UPInt upi1, upi2;
UPInt upi3 = upi1 + upi2;
upi3 = 1 + upi1;//这里不会进行隐式转换
upi3 = upi2 + 1;//这里不会进行隐式转换
- C++规定,每一个重载操作符必须获得至少一个“用户定制类型”的自变量。
条款22 考虑以操作符的复合形式op=来取代独身形式(op)
最好以 operator+= 和operator-=来实现 operator+ 和 operator-,并让函数可以执行返回值优化NRV。
class Rational{
public:
Rational& operator +=(const Rational&rhs);//并已实现
Rational& operator -=(const Rational&rhs);//并已实现
};
//且只需要维护-=,+=等就可以了。
const Rational operator+(const Rational& lhs, const Rational& rhs){
return Rational(lhs) += rhs; //这样写可以实现NRV
}
const Rational operator-(const Rational& lhs, const Rational& rhs){
return Rational(lhs) -= rhs; //这样写可以实现NRV
}
- 匿名对象更容易被消除。
条款23 考虑使用不同的程序库
不同的程序库在性能以及效率、实现方式、适用性均有不同表现,选择合适的程序库来契合不同的场景。
条款24 了解virtual functions、multiple inheritance、virtual base classes、runtime type identification成本
条款25
条款26 限制某个类可能产生的对象
不允许对象产生
将constructor设置为private 或者设置为delete。
class CantBeInstantiated{
private:
CantBeInstantiated();
CantBeInstantiated(const CantBeInstantiated&);
}
或者
class CantBeInstantiated{
public:
CantBeInstantiated() = delete;
CantBeInstantiated(const CantBeInstantiated&) = delete;
}
只允许一个对象产生(单例模式)
- 使用单例模式,即scott meyers模式。(注意不要对有单例模式的函数使用inline,会导致严重问题,inline实际上是要求该函数被调用时,使用inline函数体来替换调用函数,会导致代码复制,static静态对象也会被复制,从而导致出现多个对象,违背预期;同时也导致代码量上升。)
class A{
public:
void func();
friend Printer& thePrinter();
private:
Printer();
Printer(const Printer* lhs);
}
Printer& thePrinter(){
static Printer p;
return p;
}
- 通过类中的静态变量来限制数量,当请求的对象个数太多是跑出异常。
class Printer{
public:
class TooManyObject{};
Printer();
~Printer();
private:
static size_t numObject;
Printer(const Printer& rhs);
};
size_t Printer::numObject = 1;
Printer::Printer(){
if(numObject >= 1){
throw TooManyObject;
}
//其他操作
numObject++;
}
Printer::~Printer(){
//析构对象
--numObject;
}
允许多个对象产生
使用类模板,通过类模板的静态变量来控制对象产生。