今天早上在 O'Reilly 上偶然发现 Scott Meyers 的《Effective Modern C++》正在做半价活动,已经是最后一天了,就赶紧趁打折的机会收了这本书的 Early Release 版。接下来这段时间,我会把某些章节的快速记录整理一下,作为一个系列不定期地发在俺的 blog 上。

出于下面两个因素的考虑,我不会按原文逐字句译,而只会概括性地表述原文的思路和(必要情况下)我的理解。

  1. 这本书还处在 "pre-publication draft" 状态,其中的内容成书时可能会被微调。
  2. 逐字句译是个体力活,从个人角度讲其实不是分配精力的最优方式(懒人找起借口真是信手拈来:P)

想看精确原文的同学建议买一本,或等国内翻译(考虑到 Scott Meyers 之前发的 《Effective C++》(EC)《More Effective C++》(MEC) 的热门程度,我想国内很快就会有完全版的译文出版)。


标题中的 [EMC] 是书名的首字母缩写,目的是方便脚本自动地将相关的 markdown posts 编为一个系列(见大纲页面),后面如果有系列性的 posts 会沿袭此法,不再另行说明。

下面的内容是这本书的介绍部分(Introduction)。正如上面提到的,一些具体内容我已做过删节,如有影响阅读的跳跃或疏漏,请在评论中指出,谢谢 :)


EMC - Introduction

有经验的程序员接触 C++11 时会发现多了不少东西。auto / ranged-for / lambda/ rvalue 等功能,和对并发 (Concurrency) 的支持,改变了 C++ 的样貌。一些惯用法也变了,比如 空指针(nullptr) / 别名 (alias declaration) / 有作用域的枚举 (scoped-enum) / 智能指针等等。

但比“熟悉和了解这些新特性”更重要的是——怎样合理而灵活地运用它们。毕竟新特性的介绍网上一搜一大把,但那些谈到“如何在实际的工程环境中有效地 (Effectively) 运用”的信息,就难找得多了。这就是本书的主要内容——对于新特性,不做照本宣科的描述,而重点讲解其在实践中的运用方法。

正如前面提到的 EC 和 MEC,EMC 也是采用条款形式,共 42 条(这样可以有效保持条款间的独立性)。下面列举了部分涉及的范围:

  • 各种类型推导的方式
  • 什么时候应该(不该)用 auto?
  • 为什么要保证 const 方法的线程安全?
  • 怎么用 std::unique_ptr 实现 Pimpl 惯用法?
  • 为什么在 lambda 表达式中要避免使用默认捕获模式?
  • std::atomic 和 volatile 有啥区别?
  • 等等。

书中以条款形式回答了这些问题。值得注意的是,这些内容全部是“符合标准”且“平台无关”的可移植 C++ (Portable C++)。

这些条款以指导和参考(而非规则)的形式列出,这就意味着也有例外的存在。我们不仅需要关注条款给出的建议,更应关注其背后的基本思路,这样有助于判断“某种特殊的情形是否正对应着一个例外”的情况。本书的目标不是提供一堆“该做什么不该做什么”的教条,而是传达对于 C++11/14 的运行机制的较深层次理解。


术语和约定

这一节简要说明了一下书中会涉及到的一些术语。

首先说明了书中提到 C++ 的不同版本时所指代的含义:

  • 当提到 C++98 时,实际上指的是 C++98 和 C++03 (因为 03 只是一个相对小的修订)
  • 当提到 C++11 时,实际上指的是 C++11 和 C++14 (因为 14 只是一个相对小的修订)
  • 当提到 C++ 时,实际上指的是所有的版本

[GL] 说明一下,下面加粗的小标题是我另拟的,方便厘清行文结构,原文无。


关于 move 和 rvalue

C++11 的诸多特性中,应用最普遍的也许是 move 语义了,而 move 语义的基础是辨别右值 (rvalue,可 move) 和左值 (lvalue,不可 move)。判断表达式是否是左值,有一个简单的办法,就是看看能否取它的地址,能取地址的就是左值。这个办法有一个额外的好处,就是能提醒你,对一个特定的表达式而言,它的类型 (expression type) 跟“它是左值还是右值”是无关的。这一点的一个重要引申是,若一个函数 Foo 的参数 val 是类型 T 的右值引用(通常以 T && val 的形式出现),并不会影响“这个参数 val 本身是一个左值”这个事实——正如前面提到的,在 Foo 函数内部,可以自由地取 val 的地址。

代码示例:

class Widget { 
public: 
  Widget(Widget&& rhs);    // rhs 是左值, 虽然它有一个右值引用类型
                          
};  

关于辨别迁移构造 (move-construction) 和拷贝构造 (copy-construction)

[GL] 在中文语境下“move”相关的术语尚未有合适的译名,此处若中英混杂(如“move构造”)则略嫌生硬且影响理解,于是暂以“迁移”代之,见谅。如有更好的表达方式,还请不吝赐教。

如果一个对象 A 是由相同类型的另一个对象 B 构造而来,不论构造方式是迁移构造 (move-constructed) 还是拷贝构造 (copy-constructed) 都被认为是对象的复制 (object copy)。C++ 目前还没有专门的术语,用于区分迁移构造的对象复制 (move-constructed copy) 和拷贝构造的对象复制 (copy-constructed copy)。

代码示例:

void someFunc(Widget w);

Widget wid;
                    
someFunc(wid);                              
someFunc(std::move(wid)); 

上面的示例中,someFunc 被调了两次,第一次是拷贝构造,第二次是迁移构造。这个例子说明了一点——同一个参数类型的函数,以不同的方式调用,开销是不同的。仅仅查看被调用方的参数列表,是无法知道实际调用发生时参数传递的开销的。当然了,迁移未必一定就比复制快,还要看具体实现而定。


关于完美转发 (perfect-forwarding) 异常安全 (exception-safe) 函数对象 (function object) 和匿名函数 (lambda and closure)

[GL] 这些基本概念,相信读到这里的同学都不会有什么疑问,就不详细说了。

需要简单说一下的是异常安全分为基本保证 (basic-guarantee)强保证 (strong-guarantee)。其中前者是指:

当异常被抛出时,对应情境下的不变量应该在有效状态(也就是不能有 corrupted data structure),也没有任何外部的资源被泄漏。

而后者是指:

当异常被抛出时,程序的状态能保持跟调用函数之前完全一致([GL]也就是“零副作用”)。

「注」不变量 [invariants](http://en.wikipedia.org/wiki/Invariant_(computer_science%29), 此处还包括 Class Invariant


关于声明和定义的辨析 (declaration / definition) 函数签名 (function signature) 待移除特性 (deprecated feature) 和未定义行为 (undefined behavior)

[GL] 这些也都不展开说了。

  • 声明和定义的区分属于基本内容,此处不再赘述。
  • 函数签名的详细讨论可见俺的前一篇文章
  • 待移除特性如 std::auto_ptr,移除原因不再赘述。
  • 未定义行为是 C/C++ 特有的性能优先的产物。虽然理论上编译器在这些情况下可以做任何事,但实际上遇上这些情况编译期通常的选择是啥也不干,该怎么样就怎么样。

其它细节略。

Gu Lu
2014-09-09


[2014-09-10] Update: 文中的大纲页面的链接失效已修复,点击这里可以访问。

Comments
Write a Comment

知识共享许可协议
本作品由Gu Lu创作,采用知识共享Attribution-NonCommercial-NoDerivatives 4.0 国际许可协议进行许可。