跳到主要内容Effective C++ 读书笔记:C++ 编程习惯与最佳实践 | 极客日志C++
Effective C++ 读书笔记:C++ 编程习惯与最佳实践
本文基于 Effective C++ 整理 C++ 编程最佳实践。主要涵盖四点:一、视 C++ 为语言联邦(C、面向对象、模板、STL),根据不同场景选择合适范式;二、优先使用 const、enum、inline 替代 #define,避免宏的类型检查和副作用问题;三、尽可能使用 const 修饰符,包括迭代器、返回值及成员函数,利用 bitwise 和 logical constness 提升安全性并复用代码;四、确保对象在使用前初始化,构造函数应使用初始化列表而非赋值,并注意 static 对象的初始化顺序陷阱,推荐使用 local static 对象。
1. View C++ as a federation of languages 视 C++ 为一个语言联邦
C++ 不是'单一规则的语言',而是由 4 个核心'子语言'组成的联邦,每个子语言有专属规则和适用场景,理解这一点能避免用'单一思维'写 C++:
- C 子语言:继承自 C 的过程式编程,适用于底层高效代码(如内存操作、简单算法),遵循 C 的语法和性能规则(比如指针、数组、宏)。
- 面向对象 C++:基于 C 添加类、继承、多态等特性,适用于封装、抽象、复用的场景(比如业务逻辑的类设计)。
- 模板 C++(泛型):支持通用编程,适用于编写不依赖具体类型的代码(比如容器、算法),甚至衍生出'模板元编程(TMP)'(进阶特性,新手暂无需深入)。
- STL:基于模板实现的标准库(容器、算法、迭代器),是 C++ 的'工具集',适用于数据处理(比如用 替代原生数组)。
vector
核心价值:写 C++ 时不用'一刀切'—— 比如底层逻辑用 C 风格保证效率,业务层用面向对象保证复用,数据处理用 STL 减少重复代码。
2. Prefer const, enums, and inlines to #define 尽量用 const、enum、inline 替代 #define
#define 是预处理器指令(编译前替换),存在无类型检查、调试困难、副作用等问题,替代方案更安全、易维护:
| 场景 | #define 方案(问题) | 替代方案(优势) |
|---|
| 定义常量 | #define RATIO 1.653(无类型、不进符号表) | const double RATIO = 1.653(类型安全、可调试);class 内用 static const/enum(作用域隔离) |
| 类专属常量(旧编译器) | 无法限定作用域 | enum { NumTurns = 5 }(模拟常量,无内存开销,不可取地址) |
| 形似函数的宏 | #define MAX(a,b) f(a>b?a:b)(参数副作用) | template<class T> inline void max(T&a,T&b)(无副作用、类型检查、内联高效) |
#define ASPECT_RATIO 1.653
const double ASPECT_RATIO_THRESHOLD = 0.75;
const char* const authorName = "John Doe";
class GamePlayer {
private:
static const int NumTurns = 5;
int scores[NumTurns];
};
class GamePlayer2 {
enum{ NumTurns = 5 };
int scores[NumTurns];
};
int a=5, b=0;
#define CALL_MAX(a,b) f((a)>(b)?(a):(b))
CALL_MAX(++a, b);
template<class T> inline void callMax(T& a, T& b) {
f(a>b?a:b);
}
callMax(++a, b);
template<class T> inline void callWithMax(T& a,T& b){ f(a>b?a:b); }
- 对于单纯变量,最好用 const 对象或 enums 替换#define
- 对于形似函数的宏,最好改用 inline 函数
3. Use const whenever possible 尽可能使用 const
const 的核心是'只读约束',帮助编译器检测错误(比如意外修改值),提升代码安全性,主要有 4 个使用场景:
(1)迭代器 / 指针的 const
类比指针的 const 规则,区分两种 const:
const vector<int>::iterator iter:迭代器本身不可改(类似 T* const),但可以通过 iter 修改指向的元素;
vector<int>::const_iterator iter:迭代器指向的元素不可改(类似 const T*),但迭代器本身可以移动。
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin();
std::vector<int>::const_iterator iter1 = vec.begin();
(2)函数返回值的 const
class Rational{};
const Rational operator*(const Rational& lhs,const Rational& rhs);
(3)const 成员函数
- bitwise constness(语法层面):不修改类的非 const 成员(编译器强制检查),但存在漏洞(比如修改指针指向的内容);
logical constness(逻辑层面):允许修改 mutable 修饰的成员(客户端不可感知),比如缓存计算结果:
class CTextBlock{
public:
char& operator[](int index)const{ return pText[index]; }
private:
char* pText;
};
class CText{
public:
std::size_t length()const;
private:
char* pText;
std::size_t textLength;
bool lengthValidity;
};
std::size_t CText::length()const{
if(!lengthValidity){
textLength = strlen(pText);
lengthValidity = true;
}
return textLength;
}
(4)const/non-const 成员函数复用
避免重复代码,让 non-const 版本调用 const 版本(通过类型转换解除 const):
class TextBlock{
public:
const char& operator[](int index)const{ return pText[index]; }
char& operator[](int index){
return const_cast<char&>(static_cast<const TextBlock&>(*this)[index]);
}
private:
char* pText;
std::size_t textLength;
};
- 将某些对象声明为 const 可以帮助编译器侦测出错误用法
- 编译器强制实施 bitwise constness,但你写程序时应该遵守概念上的常量性
- 当 const 和 non-const 成员函数有实质等价的实现时,令 non-const 版本调用 const 版本可以避免代码重复
4. Make sure that objects are initialized before they are used 确保对象使用前已初始化
C++ 对内置类型(int、指针等)不保证初始化,类类型虽会调用构造函数,但错误的初始化方式会导致效率 / 逻辑问题:
(1)构造函数:初始化列替代赋值
| 方式 | 示例(构造函数内赋值) | 示例(成员初始化列) | 区别 |
|---|
| 赋值 | ABEntry(...) { theName = name; } | ABEntry2(...) : theName(name) {} | 赋值:先调用默认构造→再赋值;初始化列:直接调用拷贝构造,效率更高 |
| 强制要求 | 无 | const / 引用成员必须用初始化列(无法赋值) | 比如 const int num; 或 string& ref; 只能在初始化列初始化 |
注意:初始化列的顺序与类内成员声明顺序一致,与书写顺序无关(建议保持一致,提升可读性)。
class ABEntry{
private:
std::string theName;
std::string theAddress;
int numTimes;
public:
ABEntry(std::string name,std::string address,int numTimes){
theName = name;
theAddress = address;
numTimes = numTimes;
}
};
class ABEntry2{
private:
std::string theName;
std::string theAddress;
int numTimes;
public:
ABEntry2(std::string name,std::string address,int numTimes) :theName(name),theAddress(address),numTimes(numTimes){ }
};
(2)static 对象的初始化陷阱
static 对象(寿命从构造到程序结束)的坑:跨编译单元的 non-local static 对象初始化顺序不确定,解决方案是用 local static(函数内 static):
class FileSystem{};
FileSystem& tfs(){
static FileSystem fs;
return fs;
}
注意:local static 在多线程环境下可能有初始化竞争,需额外加锁。
- 为内置型对象进行手工初始化,因为 C++ 不保证初始化它们
- 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作 (assignment). 初值列列出的成员变量,其排列次序应该在和它们在 class 里面的声明次序相同
- 为免除'跨编译单元之初始化次序'问题,请以 local static 对象替换 non-local static 对象
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown 转 HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
- HTML 转 Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online