条款03:尽可能使用const
对成员函数的const,有两个流行概念:bitwise constness(又称physical constness)和logical constness。
bitwise constness(物理上const,二进制位const)
class CTextBlock {
public:
...
char& operator[](std::size_t position) const {
return pText[position];
}
private:
char* pText;
};
这个函数虽然的确不改变类中任何非静态成员的值,但实际上我们仍然可以用此函数返回的引用修改到该类的对象本身。如
const CTextBlock cctb("Hello");
char* pc = &cctb[0];
*pc = 'J';
如此,虽然我们设置了一个常量对象cctb
,并且只调用了它的const成员函数,但是终究还是改变了它的值。
loginal constness(逻辑上const)
认可逻辑上const的人们认为一个const成员函数可以修改它处理的对象的某些bits,但只有在客户端侦测不出(不知道发生了这件事)才可以如此。如下的CTextBlock
类有可能cache文本区块的长度以便应付询问(快速回答):
class CTextBlock {
public:
...
std::size_t length() const;
private:
char* pNext;
std::size_t textLength; // 最近一次计算的文本区块长度
bool lengthIsValid; // textLength是否还有效
};
std::size_t CTextBlock::length() const {
if (!lengthIsValid) {
textLength = std::strlen(pText); // const成员函数内赋值不被允许
lengthIsValid = true; // const成员函数内赋值不被允许
}
return textLength;
}
然而上面的代码不满足物理上的const,编译器不允许,会编译错误。虽然这两个数据的修改对const CTextBlock
对象而言可以接受。解决方法是加mutable
(可变的)关键字,它释放掉非静态成员变量的物理const(bitwise constness)约束:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char* pNext;
mutable std::size_t textLength; // 最近一次计算的文本区块长度
mutable bool lengthIsValid; // textLength是否还有效
};
std::size_t CTextBlock::length() const { ... } // 同上
non-const成员函数调用const函数提高代码复用性
class TextBlock {
public:
...
const char& operator[](std::size_t position) const {
...
return pText[position];
}
char& operator[](std::size_t position) {
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
}
条款13:以对象管理资源
RAII
“以对象管理资源”(如智能指针)的观念常被称为:RAII(Resource Acquisition Is Initialzation,资源取得时机便是初始化时机)。
RAII对象们在构造函数中获得资源,在析构函数中释放资源。
条款25:考虑写出一个不抛异常的swap函数
C++只允许对类模板偏特化,不允许对函数模板的偏特化
如:
template <typename T>
class Widget {
...
};
namespace std {
template <typename T>
void swap<Widget<T>>(Widget<T>& a, Widget<T>& b) {
...
}
}
编译错误信息:
error: non-type partial specializtion 'swap<Widget<T> >' is not allowed
条款28:避免返回handles指向对象内部成分
- handles:引用、指针、迭代器(可以用来取得某个对象)
- 对象内部成分:成员变量、不被公开使用的成员函数。
避免返回成员变量的非const引用
会导致可以通过const成员函数修改到对象内部,见条款3的例子。避免返回成员变量的非const引用,帮助const成员函数的行为像个const。
避免返回成员变量的const引用
class Point { ... };
struct RectData {
Point ulhc; // upper left-hand corner
Point lrhc; // lower right-hand corner
};
class Rectangle {
public:
...
const Point& upperLeft() const { return pData->ulhc; }
const Point& lowerRight() const { return pData->lrhc; }
...
private:
std::shared_ptr<RectData> pData;
}
class GUIObject { ... };
const Rectangle boundingBox(const GUIObject& obj);
// 用户有可能如下使用:
GUIObject* pgo;
...
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());
然而在最后一行语句结束之后,boundingBox函数返回的const Rectangle
临时对象已经被析构。所以这个指针就是一个空悬指针。
避免返回不被公开使用的成员函数
不被公开使用的成员函数也就是被声明为protected或private者。如果某成员函数返回不被公开使用的成员函数,那么后者的实际访问级别就会提高至与前者同级。
条款29:为“异常安全”而努力是值得的
异常安全函数的三种保证:基本承诺、强烈保证、不抛异常保证
- 异常安全函数(Exception-save functions)即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本承诺、强烈保证、不抛异常保证。
- 基本承诺(basic guarantee):如果异常被抛出,程序内的任何事物仍然保持在有效状态下。(但有可能处于任何状态——只要是个合法状态)
- 强烈保证(strong guarantee):如果异常被抛出,程序状态不改变。也就是说如果函数成功,就完全成功;如果函数失败,则程序会恢复到调用之前的状态。
- 不抛异常保证(nothrow guarantee):承诺绝不抛出异常,因为它们总是可以完成它们原先承诺的功能。
- “强烈保证”往往能够以 copy-and-swap 实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义(比如完成它们的代价太高)
- 函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。(除非这个函数做了一些措施来提高调用的函数的异常安全保证,比如通过备份把基本承诺变为了强烈保证)
条款34:区分接口继承和实现继承
声明纯虚函数、非纯虚函数、非虚函数的目的
- 声明一个pure virtual函数的目的是为了让derived classes只继承函数接口
- 声明简朴的(非纯)impure virtual函数的目的是让derived class继承该函数的接口和缺省实现
- 声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现
“pure virtual函数必须在derived classes中重新声明,但它们也可以拥有自己的实现”:通过对一个pure virtual函数一份定义:
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 ModelC: public AirPlane {
public:
virtual void fly(const Airport& destination) {
自己的实现...
}
...
}
条款46:需要类型转换时请为模板定义非成员函数
当编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template”内部的friend函数。
条款47:请使用traits classes表现类型信息
为什么不把这些信息直接放在对应的类里呢?
“traits必须能够施行于内置类型”意味“类型内的嵌套信息(nesting information)”这种东西出局了,因为我们无法将信息嵌套于原始指针内。因此类型的traits信息必须位于类型自身之外。
条款48:认识template元编程(TMP)
Template metaprogramming(TMP,模板元编程)可将工作从运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。
#include <iostream>
template<unsigned n>
struct Factorial {
enum { value = n * Factorial<n-1>::value };
};
template<>
struct Factorial<0> {
enum { value = 1 };
};
int main() {
std::cout << Factorial<10>::value; // 3628800
return 0;
}