差别
这里会显示出您选择的修订版和当前版本之间的差别。
| 两侧同时换到之前的修订记录 前一修订版 | |||
| public:lang:cplusplus:effective_modern_cplusplus [2026/05/07 15:07] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | public:lang:cplusplus:effective_modern_cplusplus [2026/05/07 15:07] (当前版本) – ↷ 页面public:it:cplusplus:effective_modern_cplusplus被移动至public:lang:cplusplus:effective_modern_cplusplus oakfire | ||
|---|---|---|---|
| 行 1: | 行 1: | ||
| + | ====== Effective Modern C++ ====== | ||
| + | * {{ : | ||
| + | ===== 笔记 ===== | ||
| + | |||
| + | |||
| + | ==== 导言 ==== | ||
| + | |||
| + | * 本书涉及 C++98, C++03, C++11, and C++14。 C++03与C++98相比只有技术细节差异,本书统称为C++98; | ||
| + | * 本书的条目是指导方针,而不是硬性规则,“指导方针”意味着有例外。所以掌握条目背后的原理是必要的。 | ||
| + | * 可用能否取到地址来判断一个表达式是不是左值,能取到地址就是左值,而不是看表达式的类型。比如,所有参数在内部(形式参数)都是左值,哪怕它的类型是右值引用。 | ||
| + | * 基本异常保障:符合基本异常保障的函数确保调用触发异常后,程序仍然保持正常,没有数据结构被损坏,没有资源泄露; | ||
| + | * 强异常保障符合强异常保障的函数会确保调用触发异常后,程序的运行状态和调用这个函数之前的状态是一样的。 | ||
| + | * 函数对象:指重载了'' | ||
| + | * 本书把**函数签名**定义为函数的参数与返回值,不包括函数名与其它声明(比如 noexcept、constexpr等),即相同参数与相同返回值类型的函数是同一个函数签名。这与官方定义不同,官方定义不包括返回值类型。 | ||
| + | * 要规避标准提及的未定义行为。 | ||
| + | * 内置指针:new 返回的原始指针; | ||
| + | |||
| + | ==== 章节1. 类型推导(Deducing types) ==== | ||
| + | === » 条目1. 理解模板的类型推导 === | ||
| + | * '' | ||
| + | * 当模板函数形参为指针或非通值引用,例如'' | ||
| + | * 当模板函数形参为**通值引用** '' | ||
| + | * 当模板函数形参为非指针非引用 '' | ||
| + | * 数组实参为例外情况:模板函数形参按值传递时,实参如果为数组,形参会被推导为指针;模板函数形参为引用时,则保留数组类型不会退化为指针 | ||
| + | * 函数类型退化为指针与数组情况一样. | ||
| + | * 列个表: | ||
| + | |||
| + | <WRAP tablewidth 80% center> | ||
| + | ^ 形参 | ||
| + | | f(T*), f(T&), f(const T& | ||
| + | | f(T*), f(T&), f(const T& | ||
| + | | f(T*), f(T& | ||
| + | | f(const T& | ||
| + | | f(T&& | ||
| + | | f(T&& | ||
| + | | f(T param) | ||
| + | | f(T param) | ||
| + | | f(T& param) | ||
| + | | f(T param) | ||
| + | | f(T& param) | ||
| + | </ | ||
| + | |||
| + | === » 条目2. 理解 auto 的类型推导 === | ||
| + | * '' | ||
| + | * '' | ||
| + | === » 条目3. 理解 decltype 的类型推导 === | ||
| + | * '' | ||
| + | * 对于'' | ||
| + | * 对于'' | ||
| + | * C++14 支持 '' | ||
| + | === » 条目4. 了解推导类型结果的查看方法 === | ||
| + | * 使用 IDE 提示、编译错误信息或使用 '' | ||
| + | * 但以上工具的结果可能既无帮助也不准确,因此了解类型推导规则是必要的。 | ||
| + | |||
| + | ==== 章节2. auto 关键字 ==== | ||
| + | === » 条目5. 优先使用 auto 而非显式类型声明 === | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * 但是 '' | ||
| + | === » 条目6. 当 auto 推导不出期望类型时,使用显式类型初始化 === | ||
| + | * '' | ||
| + | * 类似'' | ||
| + | * 也可使用类型转换来让 '' | ||
| + | |||
| + | ==== 章节3. 转向现代C++ ==== | ||
| + | |||
| + | === » 条目7. 创建对象(定义变量初始化)时区分 '' | ||
| + | * '' | ||
| + | * 花括号初始化方式适用性最广,比如在类atomic成员声明时初始化就只能用花括号方式;、 | ||
| + | * 花括号初始化可规避内置类型的隐式转换 | ||
| + | * 花括号初始化可避免语法解析的歧义,比如 '' | ||
| + | * 但是,在构造函数重载解析时,花括号初始化会尽可能匹配到 '' | ||
| + | * 括号初始化与花括号初始化之间产生巨大差异的一个例子是带有两个参数的 '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * 在模板对象创建时,用括号还是花括号是可能具有挑战性的事情(由于模板可变参数的存在) | ||
| + | === » 条目8. 相比 0 或 NULL优先选择使用 nullptr === | ||
| + | * 优先使用 nullptr 可避免歧义 | ||
| + | * 要避免同时重载整形与指针类型 | ||
| + | === » 条目9. 相比 typedefs 优先选择使用别名 === | ||
| + | * 别名用于声明函数指针时更直观:< | ||
| + | // FP is a synonym for a pointer to a function taking an int and | ||
| + | // a const std:: | ||
| + | typedef void (*FP)(int, const std:: | ||
| + | // typedef same meaning as above | ||
| + | using FP = void (*)(int, const std:: | ||
| + | * 别名可直接用于模板,而 '' | ||
| + | * 由于历史原因,C++11 保留有许多 typedef 的类型,例如 '' | ||
| + | === » 条目10. 使用枚举时优先使用限定域枚举 === | ||
| + | * c++98 的枚举是非限定域的,会污染全局变量命名 | ||
| + | * 限定域的枚举类'' | ||
| + | * c++98 enum 不限定默认基础类型,编译器在定义枚举时才确定枚举的基础类型;'' | ||
| + | * 限定域枚举可以前置声明 '' | ||
| + | === » 条目11. 阻止调用时,相比私有域声明不定义方式,优先使用函数删除的方式 === | ||
| + | * 删除默认构造函数与默认赋值重载函数:< | ||
| + | Widget& operator=(const Widget& | ||
| + | * 任何函数都可以被删除,包括非成员函数与模板。 | ||
| + | === » 条目12. 声明覆盖函数时,使用修饰词 override === | ||
| + | * 区分覆盖和重载的区别。 | ||
| + | * c++11 比 c++98 对虚函数覆盖多了一条限定:覆盖函数的引用限定词必须与基类相同。< | ||
| + | class Widget { | ||
| + | public: | ||
| + | … | ||
| + | void doWork() &; | ||
| + | void doWork() &&; | ||
| + | }; | ||
| + | … | ||
| + | Widget makeWidget(); | ||
| + | Widget w; // normal object (an lvalue) | ||
| + | w.doWork(); | ||
| + | makeWidget().doWork(); | ||
| + | </ | ||
| + | * '' | ||
| + | === » 条目13. 优先使用 const 迭代器 const_iterator === | ||
| + | * 相比c++98, | ||
| + | * 在编写最泛化代码时,更偏好非成员版本的'' | ||
| + | * c++14 相比 c++11 添加了 '' | ||
| + | === » 条目14. 如果函数不会抛出异常,则声明为 noexpect === | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * 大多数函数都是异常中性(exception-neutral), | ||
| + | * 从实现复杂度考虑,只对有不抛异常自然实现的函数来声明 '' | ||
| + | === » 条目15. 尽可能使用 constexpr === | ||
| + | * '' | ||
| + | * '' | ||
| + | * 由于在编译期变成了常量,constexpr的对象或函数可用在仅允许常量的语境下。 | ||
| + | * '' | ||
| + | === » 条目16. 确保 const 成员函数线程安全 === | ||
| + | * [[https:// | ||
| + | * 确保 const 成员函数的线程安全,除非保证不会使用在并发环境。 | ||
| + | * '' | ||
| + | === » 条目17. 理解特制成员函数的生成机制 === | ||
| + | * 特制成员函数(special member function)是指编译器可自行生成的: | ||
| + | * 默认构造函数,析构函数,拷贝构造函数,拷贝赋值运算, | ||
| + | * 移动构造函数: | ||
| + | * 移动赋值运算:'' | ||
| + | * 特制成员函数仅在需要时才会生成,具有public, | ||
| + | * 移动操作(指移动构造与移动赋值运算,下同)仅生成于该类未显式声明任何拷贝操作、移动操作与析构函数时。 | ||
| + | * 拷贝构造函数仅生成于该类未显式声明拷贝构造函数,并在声明了移动操作后会被删除。 | ||
| + | * 拷贝赋值运算仅生成于该类未显式声明拷贝赋值运算,并在声明了移动操作后会被删除。 | ||
| + | * 类显示声明析构函数后,生成的拷贝操作被废弃。 | ||
| + | * 模板成员函数的声明(比如 '' | ||
| + | |||
| + | ==== 章节4. 智能指针 ==== | ||
| + | * C++ 11 存在四种智能指针: | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | === » 条目18 使用 std:: | ||
| + | * '' | ||
| + | * 很适合作为工厂设计模式的返回:工厂自定义资源删除方法, | ||
| + | * '' | ||
| + | * '' | ||
| + | === » 条目19 使用 std:: | ||
| + | * '' | ||
| + | * 引用计数的读写是原子性的,存在额外开销; | ||
| + | * '' | ||
| + | * '' | ||
| + | // 自定义删除器 | ||
| + | auto loggingDel = [](Widget *pw) { | ||
| + | makeLogEntry(pw); | ||
| + | delete pw; | ||
| + | }; | ||
| + | // | ||
| + | std:: | ||
| + | // | ||
| + | std:: | ||
| + | </ | ||
| + | * 自定义删除器不增加'' | ||
| + | |||
| + | * 控制块与共享资源要确保一一对应,创建时机: | ||
| + | * '' | ||
| + | * 从'' | ||
| + | * 从原生指针构造'' | ||
| + | * 避免从原生指针构造'' | ||
| + | * '' | ||
| + | * 和'' | ||
| + | === » 条目20 使用 std:: | ||
| + | * '' | ||
| + | auto spw = std:: | ||
| + | std:: | ||
| + | spw = nullptr; | ||
| + | wpw.reset(); | ||
| + | </ | ||
| + | * '' | ||
| + | * '' | ||
| + | std:: | ||
| + | std:: | ||
| + | </ | ||
| + | * '' | ||
| + | * 缓存:资源过期就创建新的,否则就使用老的,缓存里的指针不参与生命周期; | ||
| + | * 观察者列表:被观察者不关心观察者的死活,观察者注销不需要关心被观察者的列表注销; | ||
| + | * 避免std:: | ||
| + | === » 条目21 比起new, 优先使用 std:: | ||
| + | * '' | ||
| + | * 使用new 的版本'' | ||
| + | * 考虑这个代码< | ||
| + | processWidget(std:: | ||
| + | </ | ||
| + | - 执行 new Widget | ||
| + | - 执行 computePriority() | ||
| + | - 构造 std:: | ||
| + | * 此时,如果第二步 执行computePriority() 时产生了异常导致第三步未执行,那么第一步的 new 就产生了泄露。 | ||
| + | * 所以,如果使用 new 方式,应把 new 结果直接传递给智能指针构建,确保这中间没有其它语句。 | ||
| + | * '' | ||
| + | * 不适合使用 '' | ||
| + | - 需要自定义删除器; | ||
| + | - 希望用花括号初始化。 | ||
| + | * 不建议使用 '' | ||
| + | - 有自定义内存管理的类; | ||
| + | - 特别关注内存的系统; | ||
| + | - 非常大的对象并且 '' | ||
| + | | ||
| + | === » 条目22 使用 Pimpl 惯用法时,定义特制成员函数需要放在实现文件里=== | ||
| + | * **Pimpl**(pointer to implementation)惯用法,为了缩减编译时间而减少头文件依赖 | ||
| + | * 对于 '' | ||
| + | // 以下都在实现文件 widge.cpp 中: | ||
| + | Widget:: | ||
| + | // 自定义析构导致拷贝与移动构造不能默认生成,手动生成需定义在实现文件 | ||
| + | Widget:: | ||
| + | Widget& Widget:: | ||
| + | </ | ||
| + | * '' | ||
| + | |||
| + | ==== 章节5. 右值引用、移动语义和完美转发 ==== | ||
| + | * **形参永远是左值**,即使它的类型是右值引用(参考上方导言第三行) | ||
| + | === » 条目23 理解 std::move 与 std:: | ||
| + | * '' | ||
| + | // std::move 的简单实现: | ||
| + | template< | ||
| + | decltype(auto) move(T&& | ||
| + | { | ||
| + | using ReturnType = remove_reference_t< | ||
| + | | ||
| + | }</ | ||
| + | * 如果想移动实参,就不要把实参声明为 '' | ||
| + | * '' | ||
| + | * '' | ||
| + | === » 条目24 区分通值引用与右值引用 === | ||
| + | * 通值引用,两种情况: | ||
| + | * 函数模板参数为 '' | ||
| + | * 变量声明为 '' | ||
| + | * 如果类型声明格式不是标准的'' | ||
| + | void f(Widget&& | ||
| + | Widget&& | ||
| + | auto&& | ||
| + | template< | ||
| + | void f(std:: | ||
| + | template< | ||
| + | void f(T&& | ||
| + | </ | ||
| + | * 通值引用如果是被右值初始化,那么就转为右值引用,左值则转为左值引用(详见类型推导)。 | ||
| + | === » 条目25 std::move 使用在右值引用,std:: | ||
| + | * 要利用形参的右值性时,右值引用的形参使用 '' | ||
| + | * 一个函数内想多次利用同一个对象的右值性时,只在最后一次使用 '' | ||
| + | * 按值返回的函数要返回右值引用或通值引用时,同样使用 '' | ||
| + | * 由于标准规定编译器存在返回值优化(return value optimization,RVO)以及在不优化场景下将 '' | ||
| + | |||
| + | === » 条目26 避免重载通值引用 === | ||
| + | * 相比 '' | ||
| + | template< | ||
| + | void logAndAdd(int idx){} // overload_func | ||
| + | |||
| + | short index; | ||
| + | logAndAdd(index); | ||
| + | </ | ||
| + | * 完美转发构造函数尤其如此,因为对于非const左值来说,它们通常比复制构造函数(复制构造函数参数声明为const)更匹配,而且它们可以劫持派生子类调用基类的复制与移动构造函数,转而让子类调用完美转发构造函数。 | ||
| + | === » 条目27 熟悉「重载通值引用」之外的替代方法 === | ||
| + | |||
| + | ==== 翻译对照 ==== | ||
| + | <WRAP tablewidth 50% > | ||
| + | ^ 英 ^ 汉 ^ 备注 | ||
| + | | argument | ||
| + | | parameter | ||
| + | | basic guarantee | ||
| + | | strong guarantee | ||
| + | | built-in pointer | ||
| + | | raw pointer | ||
| + | | universal reference | ||
| + | | scoped enums | 限定域枚举 | ||
| + | | unscoped enums | 非限定域枚举 | ||
| + | | forward-declared | ||
| + | | override | ||
| + | | overload | ||
| + | | maximally | ||
| + | | special member function | ||
| + | | | ||
| + | | | ||
| + | | | ||
| + | | | ||
| + | </ | ||