2013年12月31日 星期二

[books] The Design and Evolution of C++ 第二部分 - 讀後心得



相關連結:
[books] The Design and Evolution of C++ 第一部分 - 讀後心得

這篇心得是剩下的第二部份 (10 ~ 18 章)

目录
第0章 致读者
第1部分
第1章 C++的史前时代
1.1 Simula和分布式系统
1.2 C与系统程序设计
1.3 一般性的背景

第2章 C with Classes
2.1 C with Classes的诞生
2.2 特征概览
2.3 类
2.4 运行时的效率
2.5 连接模型
2.5.1 纯朴的实现
2.5.2 对象连接模型
2.6 静态类型检查
2.6.1 窄转换
2.6.2 警告的使用
2.7 为什么是C
2.8 语法问题
2.8.1 C声明的语法
2.8.2 结构标志与类型名
2.8.3 语法的重要性
2.9 派生类
2.9.1 没有虚函数时的多态性
2.9.2 没有模板时的容器类
2.9.3 对象的布局模型
2.9.4 回顾
2.10 保护模型
2.11 运行时的保证
2.11.1 构造函数与析构函数
2.11.2 存储分配和构造函数
2.11.3 调用函数和返回函数
2.12 次要特征
2.12.1 赋值的重载
2.12.2 默认实参
2.13 考虑过,但是没有提供的特征
2.14 工作环境

第3章 C++的诞生
3.1 从C with Classes到C++
3.2 目标
3.3 Cfront
3.3.1 生成C
3.3.2 分析C++
3.3.3 连接问题
3.3.4 Cfront发布
3.4 语言特征
3.5 虚函数
3.5.1 对象布局模型
3.5.2 覆盖和虚函数匹配
3.5.3 基成员的遮蔽
3.6 重载
3.6.1 基本重载
3.6.2 成员和友元
3.6.3 运算符函数
3.6.4 效率和重载
3.6.5 变化和新运算符
3.7 引用
3.8 常量
3.9 存储管理
3.10 类型检查
3.11 次要特征
3.11.1 注释
3.11.2 构造函数的记法
3.11.3 限定
3.11.4 全局变量的初始化
3.11.5 声明语句
3.12 与经典C的关系
3.13 语言设计工具
3.14 《C++程序设计语言》(第一版)
3.15 有关“什么是”的论文

第4章 C++语言设计规则
4.1 规则和原理
4.2 一般性规则
4.3 设计支持规则
4.4 语言的技术性规则
4.5 低级程序设计支持规则
4.6 最后的话

第5章 1985-1993年表
5.1 引言
5.2 Release 2.
5.3 带标注的参考手册(ARM)
5.4 ANSI和ISO标准化

第6章 标准化
6.1 什么是标准
6.1.1 实现细节
6.1.2 现实的检查
6.2 委员会如何运作
6.3 净化
6.3.1 查找问题
6.3.2 临时量的生存期
6.4 扩充
6.4.1 评价准则
6.4.2 状况
6.4.3 好扩充的问题
6.4.4 一致性
6.5 扩充建议实例
6.5.1 关键词实参
6.5.2 受限指针
6.5.3 字符集

第7章 关注和使用
7.1 关注和使用的爆炸性增长
7.1.1 C++市场的缺位
7.1.2 会议
7.1.3 杂志和书籍
7.1.4 编译器
7.1.5 工具和环境
7.2 C++的教与学
7.3 用户和应用
7.3.1 早期用户
7.3.2 后来的用户
7.4 商业竞争
7.4.1 传统语言
7.4.2 更新一些的语言
7.4.3 期望和看法

第8章 库
8.1 引言
8.2 C++库设计
8.2.1 库设计的折中
8.2.2 语言特征和库的构造
8.2.3 处理库的多样性
8.3 早期的库
8.3.1 I/O流库
8.3.2 并行支持
8.4 其他库
8.4.1 基础库
8.4.2 持续性和数据库
8.4.3 数值库
8.4.4 专用库
8.5 一个标准库

第9章 展望
9.1 引言
9.2 回顾
9.2.1 C++在其预期领域取得了成功吗?
9.2.2 C++是不是一种统一的语言?
9.2.3 什么是最大失误?
9.3 仅仅是一座桥梁吗?
9.3.1 在一个很长的时期我们还需要这座桥梁
9.3.2 如果C++是答案,那么问题是什么?
9.4 什么能使C++更有效
9.4.1 稳定性和标准
9.4.2 教育和技术
9.4.3 系统方面的问题
9.4.4 在文件和语法之外
9.4.5 总结

第2部分
第10章 存储管理
10.1 引言
10.2 将存储分配和初始化分离
10.3 数组分配
10.4 放置
10.5 存储释放问题
10.6 存储器耗尽
10.7 自动废料收集
10.7.1 可选的废料收集
10.7.2 可选择的废料收集应该是什么样子的?

第11章 重载
11.1 引言
11.2 重载的解析
11.2.1 细粒度解析
11.2.2 歧义控制
11.2.3 空指针
11.2.4 overload关键字
11.3 类型安全的连接
11.3.1 重载和连接
11.3.2 C++连接的一种实现
11.3.3 回顾
11.4 对象的建立和复制
11.4.1 对复制的控制
11.4.2 对分配的控制
11.4.3 对派生的控制
11.4.4 按成员复制
11.5 记法约定
11.5.1 灵巧指针
11.5.2 灵巧引用
11.5.3 增量和减量的重载
11.5.4 重载 ->*
11.5.5 重载逗号运算符
11.6 给C++增加运算符
11.6.1 指数运算符
11.6.2 用户定义运算符
11.6.3 复合运算符
11.7 枚举
11.7.1 基于枚举的重载
11.7.2 布尔类型

第12章 多重继承
12.1 引言
12.2 普通基类
12.3 虚基类
12.4 对象布局模型
12.4.1 虚基布局
12.4.2 虚基类和强制
12.5 方法组合
12.6 有关多重继承的论战
12.7 委托
12.8 重命名
12.9 基类和成员初始化

第13章 类概念的精练
13.1 引言
13.2 抽象类
13.2.1 为处理错误而用的抽象类
13.2.2 抽象类型
13.2.3 语法
13.2.4 虚函数和构造函数
13.3 const成员函数
13.3.1 强制去掉const
13.3.2 const定义的精练
13.3.3 可变性与强制
13.4 静态成员函数
13.5 嵌套的类
13.6 Inherited::
13.7 放松覆盖规则
13.8 多重方法
13.9 保护成员
13.10 改进代码生成
13.11 指向成员的指针

第14章 强制转换
14.1 主要扩充
14.2 运行时类型信息
14.2.1 问题
14.2.2 dynamic_cast运算符
14.2.3 RTTI的使用和误用
14.2.4 为什么提供一个“危险特征”
14.2.5 typeid()运算符
14.2.6 对象布局模型
14.2.7 一个例子:简单的对象I/O
14.2.8 考虑过的其他选择
14.3 强制的一种新记法
14.3.1 问题
14.3.2 static_cast运算符
14.3.3 reinterpret_cast运算符
14.3.4 const_cast运算符
14.3.5 新风格强制的影响

第15章 模板
15.1 引言
15.2 模板
15.3 类模板
15.4 对模板参数的限制
15.4.1 通过派生加以限制
15.4.2 通过使用加以限制
15.5 避免代码重复
15.6 函数模板
15.6.1 函数模板参数的推断
15.6.2 描述函数模板的参数
15.6.3 函数模板的重载
15.7 语法
15.8 组合技术
15.8.1 表述实现策略
15.8.2 描述顺序关系
15.9 模板类之间的关系
15.9.1 继承关系
15.9.2 转换
15.9.3 成员模板
15.10 模板的实例化
15.10.1 显式的实例化
15.10.2 实例化点
15.10.3 专门化
15.10.4 查找模板定义
15.11 模板的作用
15.11.1 实现与界面的分离
15.11.2 灵活性和效率
15.11.3 对C++其他部分的影响

第16章 异常处理
16.1 引言
16.2 目标和假设
16.3 语法
16.4 结组
16.5 资源管理
16.6 唤醒与终止
16.7 非同步事件
16.8 多层传播
16.9 静态检查
16.10 不变式

第17章 名称空间
17.1 引言
17.2 问题
17.3 解决方案的思想
17.4 一个解决方案:名称空间
17.4.1 有关使用名称空间的观点
17.4.2 使名称空间投入使用
17.4.3 名称空间的别名
17.4.4 利用名称空间管理版本问题
17.4.5 细节
17.5 对于类的影响
17.5.1 派生类
17.5.2 使用基类
17.5.3 清除全局的static
17.6 与C语言的兼容性
第18章 C语言预处理器
索引 

繼續第二部份, 主要在談 c++ 的特性, 沒有依照實作時間的順序在討論這些特性。

10.3 array allocation 提到為 array 提供了
operator new[]
operator delete[]
最早由 Laura Yaker 所推動。

10.5 提及了可以有很多 operator new, 但是卻只有一個 operator delete, 建構函式/解構函式也很類似, 為什麼採取這樣的非對稱作法, 大致原則是, 如果要提供 overloaded operator delete, 那麼就必須知道怎麼 new 這個 object, 這麼會帶來一些麻煩, 讓 compiler 處理應該是容易多了。Bjarne 自己也不確定這個決定是不是正確, 這是個難題, 既然他是 c++ 發明人, 他說了就算。

delete [] 曾經需要把 array 個數帶入, ex:
delete[10] p2;
不過這看來不是好主意是吧? 還是轉嫁給 c++ 語言好了。

10.7 列出了使用 garbage collection 的優缺點, 如果使用 gc 能不付出任何代價, 相信沒有人會反對, 這就是使用 gc 的困難點, 也是 c++ 在效率勝出有 gc 功能的語言原因之一, 有了 gc, 很可能 c++ 便無法用來開發低階的系統程式, 但開發低階的系統程式這可是 c++ 原本的設計目標, 真是兩難, c++11 已經有了 gc 的加入, 但目前 (2013) 還沒有任何實作品。看看這些聰明的人如何搞定這個難題。

p201 提到了
operator<<(int)
put(char c)
在還沒有將 char, int 區分時 (為了相容 c, c 把 char, int 看成一樣), 在 stream library 上是一個問題,
operator<<(int)
operator<<(char c)
這樣是不會被當成 overloaded function, 而在 c++ 區分了 char, int 後, 竟然沒有遭遇到和 c 的相容性問題, 真是幸運, 不過 sizeof('a') 是個例外。

ref: http://descent-incoming.blogspot.tw/2013/12/c-character-literal-is-different-from-c.html

p202 提到 strtok function prototype

char *strtok(char*, const char*)
const char *strtok(const char*, const char*)

ansi c
char *strtok(const char*, const char*)

不過在 linux man page 我看到的是:
char *strtok(char *str, const char *delim);

這裡是要說明根據 const 來提供 overloaded function, 不過對於 strtok function prototype 我沒能理解。

11.2.3 提到要怎麼表現一個空指標, 大家都很習慣 c NULL macro。

NULL 通常是 (void *)0

在 c++ 這行不通:

char *p = (void*)0;

np.cpp: In function ‘int main(int, char**)’:
np.cpp:4:20: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
char *p = (void*)0;

所以 c++ 建議用 0, 而不要用 NULL, c++11 引入了 nullptr 解決這個問題。

11.2.4 介紹了 overload keyword, 這會引發一些災難, 應該很少人用過這個 keyword, 現在已經沒有這個 keyword 了。

11.3 為了提供安全的連結所下的苦心, 得與 c 相容, 不能有執行/編譯/連結的額外開銷, 這可真的不容易, 所以有了 mangle, extern "C" 這樣的方式, 現在看來很當然爾, 在當時可是難倒了這些聰明的人, 最後才想出來的聰明辦法。

mangle 將 function 的 type 也一起當成 function 的編碼名稱, 這可以拿來作為函式參數的型別檢查, 我疑惑的是為什麼沒有考慮函式的傳回值?

11.5.3 提到 prefix++, postfix++, 在 cfront release 1.0 是沒有這樣的區分, 為了要搞定 prefix, postfix overload 問題, 曾經有過這樣的想法:

X& operator prefix++();
X operator postfix++();

X& prefix operator++();
X postfix operator++();

我個人還蠻喜歡這個
X& ++operator();
X operator++();

X& operator++();
X operator++();

這是現在用的版本
X& operator++();
X operator++(int);

11.6.1 是否要為 c++ 提供指數運算符號 (**), 也引起各方爭議, 引進 ** 可沒想像中簡單, b**c**d 的優先權就得傷透腦筋, 最後不玩了。

11.6.2 竟然提到是否要提供使用者自行定義的運算符號, c++ 的野心還真大, 不過帶來的問題似乎太大最後並不支援這樣的想法。

11.6.3 提到的 composite operators 很有趣, 直接給書上例子:
Matrix a, b, c, d;

a=b*c+d;

Matrix operator = * + (Matrix &, Matrix &, Matrix &, Matrix &)

直接對應這個運算。

第 12 章提到多重繼承, 開頭有一段話, 「因為你有父親和母親」, 這大概是為了解釋為什麼 c++ 要提供重繼承吧! 不過令我意外的是: Bjarne 似乎不怎麼急著把多重繼承這特徵加進來。12.1 提到多重繼承先被實作, 後來才是 template, exception 則最晚。而 Bjarne 自己比較想要 template 這功能。1982 Bjarne 思考多重繼承的問題, 1984 發現有效的實作方式。

Ole-Johann Dahl (simula 發明人, 2001 圖靈獎得主) 1996 就有考慮多重繼承, 不過因為會使 garbage collection 變得更複雜而取消。

12.4 The object layout model 說明多重繼承的兩個實作方式 (有 virtual base class, virtual function), p236 提到一種 thunk 的實作方式, 用來調整 this; 深度探索 c++ 物件模型繁體中文版 p162 ~ p163 也提到 thunk, 順道提及了將 thunk 倒拼便是 knuth, 不過 th 的倒拼是 ht, 這似乎有點勉強要和 Knuth 扯上關係。

12.4.1 解釋了 virtual base class 的 virtual 所謂何來?

12.6 提出了 8 點在說明多重繼承的爭論, 有人不喜歡多重繼承 ... 有好多的理由, 不過 Bjarne 還是把多重繼承加進來了。

p255 提到 mutable 並沒有像想像中那麼有用, 還是有機會要出動 const cast 消除 const 語意。

p256 展示了這個程式碼:
((X*)0)->f()
這個就是 static member function, 程式設計師真的很聰明, 就算沒有這特性, 總是可以找到一些方法來達成, 當然在這功能加入了, 就更安全, 也有較好的語法。

13.6 的 inherited keyword 本來已經打算引入, 不過在 Michael Tiemann 提供以下程式碼後, 就不需要了。

inherited.cpp
 1 class employee
 2 {
 3   public:
 4   virtual void print()
 5   {
 6   }
 7 };
 8
 9 class foreman: public employee
10 {
11   public:
12   typedef employee inherited;
13   void print()
14   {
15   }
16 };
17
18 class manager: public foreman
19 {
20   public:
21   typedef foreman inherited;
22   void print();
23 };
24
25 void manager::print()
26 {
27   inherited::print();
28 }

13.8 c++ 不支援 multi-method, 書上舉了 intersect 來處理 class Shape, Rectangle, Circle, 我不理解這是要表達什麼, 13.8.1 提供了 wordaround 的方法支援 multi-method。

13.11 pointers to member, 指向 class member 的指標, 在 cfront release 1.2 之後才提供這個功能, 它不僅僅是一個 offset 而已, 是個很複雜的東西。可以參考這篇: http://descent-incoming.blogspot.tw/2013/08/c-member-function-pointer.html

chapter 14 的主題是型別轉換, 簡體中文版本把 cast 翻譯成強制, 真的是很難理解其意思, 應該要翻做強制轉換比較貼近書中的意思。重要的 RTTI 也在這章有主要的說明。

14.1 開頭就提到 C++ 4 個 major extension:
  1. templates
  2. exceptions
  3. run-type type information
  4. namespace

p272 提到 Bjarne 用 2 個上午實作了實驗性質的 RTTI 版本, 說到 RTTI 比 exception, template 簡單兩個數量級, 比多重繼承簡單一個數量級, 所以現在知道哪個 C++ 特性很難搞了吧! 而 Lenkov, 1991 這篇論文則是要把 rtti 加入 c++ 的動力。

14.2.2.1 介紹了當初討論的 cast 語法:
Checked(p)
Unchecked(p)

(?T*)p
(virtaul T*)p

現在的語法是
dynamic_cast(p)

14.2.5.1 說明了 class type_info, 儘量做到最小, 裡頭有個 before() 用來排序用。

14.2.6 說到, cfront release 2.0 保留了 RTTI 實作所需的部份, 但是並沒有加入 RTTI, Bjarne 在測試過 RTTI 實作的部份後就刪除了。

14.2.7 提到的 object I/O, 和深入淺出 MFC 提到的 dynamic creation 似乎是類似的東西, 原來這麼早就有這樣的想法了。

14.3 介紹那 4 個 cast 語法,
static_cast
dynamic_cast
const_cast
reinterpret_cast
設計的這麼「醒目」, 其中有個目的是希望可以透過 grep 來查詢使用到哪些 cast。

p294 說明了下面的程式碼使用 reinterpret_cast, static_cast 對同一個指標做轉換會得到不同結果。

class A{};
class B{};
class D: public A, public B{};
void f(B *pb)
{
D* pd1 = reinterpret_cast(pb);
D* pd2 = static_cast(pb);
}

chapter 15 的主題是 template, 最初的 c++ 設計就考慮了 parameterized types, 不過因為複雜度和其他原因被延遲到後面才實作。當時參考了 smalltalk 和 clu 的作法, 效率不好的 smalltalk 實作方式自然被捨棄了, 所以 c++ template 比較像 clu 的作法。

15.7 template syntax, 一開始是設計程這樣:
class template
class vector

funcion template
T& index(vector &v, int i)
{
}

不過最後還是導入了 template keyword。

15.11.3 提到內建型別也有建構/解構函式, ex:


int i = int(2);

chapter 16 談論的是 exception handling, 和 template 一樣, 在最初的 c++ 設計中就考慮了, 也一樣因為複雜度的關係被延遲到後期才實作。
[Koenig, 1989b], [Koenig, 1990], [Cameron, 1992] (16.9.1 實作) 有相關的討論。

16.3 exception handleing 原本不想提供 3 個 keyword, 只打算使用 catch 表示拋出/捕捉 exception, 但 Bjarne 沒能說服其他人, 所以有了 thorw, 而 raise, signal 也因為是 c 標準程式庫而不能用了。

exception handle 翻譯為處理器, 我一開始還以為是 cpu 之類的東西。

16.6 resumption vs termination, 對於 exception 要設計成 resumption 或是 termination 在委員會多有爭論。resumption 的實作可以在排除問題之後回到發生 exception 的當下, 這麼美好的作法很有吸引力, Jim Mitchell 卻說這是站不住腳的。考慮一個作法是要非常謹慎的, 需要思考很多面向, 看看 Bjarne 的頭就知道了, 而且參考了很多業界的大型專案數據, 有數據才有說服力, 而不是僅僅自己的自認為意見, IBM, sum, microsoft 都提供了他們的經驗數據, 最後 c++ 委員會採納 termination 的作法。

16.6.1 wordaround for resumption 則提供了一個作法來達到類似 resumption 的功能。

chapter 17 namespace, 17.1 199307 namespace 正式加入了 c++。17.2 提到使用 class 當作 namespace 的一些問題, 比如說: global function 需要使用 static, 這感覺就是哪裡怪怪的。

17.4 在設計 namespace 初期時的語法:
namespace A
{
void f();
void g();
void h();
}

using A; // now is : using namespace A;

using A::(f,g,h);
果然很難看。

17.6 提到 namespace 和 c function 的問題:

n.cpp
 1 #include <iostream>
 2
 3 using namespace std;
 4
 5 namespace X
 6 {
 7   extern "C" void f(int i)
 8   {
 9     cout << "X:: " << i << endl;
10   }
11 }
12
13
14 namespace Y
15 {
16   extern "C" void f(int i)
17   {
18     cout << "Y:: " << i << endl;
19   }
20 }
21
22 int main(int argc, char *argv[])
23 {
24
25   return 0;
26 }

g++ 的錯誤訊息
descent@debian-vm:namespace_c_func$ g++ n.cpp
/tmp/ccWPK35h.s: Assembler messages:
/tmp/ccWPK35h.s:42: Error: symbol `f' is already defined

我從來沒想過這樣的問題, 不過我能理解會有這樣的錯誤訊息, 這是 c 語言的問題, 而不是 c++ namespace 的問題, 真是討人厭的 c 兼容性。

chapter 18 頁數不多, 主題是 c preprocessor (cpp), Bjarne 不喜歡 cpp, 想要廢掉 cpp, 我終於知道為什麼他不鼓勵 c++ 程式員用 c preprocessor。不過到了 2014, cpp 也還是存在 c++ 程式中。cpp 提供了不少功能, c++ 的某些特性可以代替它, 但還沒有什麼好辦法可以完全不使用它。

終於看完了, 可以了解自己喜歡的語言是怎麼設計和演化真是一件開心的事情, 本書在 c++ 程式技術著墨不多, 最主要在說明為什麼 c++ 會設計成這個樣子, 在設計一個特性時, 考慮了哪些因素, 這些都不是容易的事情, 有個東西沒想到, 就會有大麻煩出現, 尤其 c++ 又要考慮效率, 這是最主要, 又要有 c 的相容性, 這真是難倒這些大師們。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。