2014年1月1日 星期三

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



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

自然, 我讀的是簡體中文版本: C++语言的设计与演化, 20131015 委託代買, 20131021 拿到, 60.2/86 rmb, 實際款項 83+60.2*5.1 = 390 nt, 不貴, 在台灣書店買可能需要 86*5 = 430 nt, 重點是我在台灣找不到這本書。這篇心得只是第一部份 (1 ~ 9 章)。

目录
第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语言预处理器
索引 

原文版出版日期: 199405, 簡體中文出版於 2002 年, 我買的是 201209 再版第二刷 (這樣的好書總算在十年後再版, 對無法看原文書的我來說, 實在珍貴)。英文版我好早就已經買了, 不過卻不曾好好閱讀過, 當然是我的英文閱讀能力問題。

The C++ Programming Language (3rd) 我已經讀過 (當然是中文版本), 再來應該讀讀 c++ 的歷史了。這是一本在說明 c++ 的歷史以及 c++ 為什麼會這樣設計的回憶錄。喜歡 c++ 的程式員應該會有興趣讀這本書。

一開始 Bjarne 談及使用 Simula 來開發他博士論文需要的模擬器與相關的 os 系統。

Simula 很好, 也很不好 (效率問題), 最後改用 BCPL 重新把模擬器改寫, 得到了需要的數據, 也順利取得博士學位。

這時候他對程式語言有了自己的一套想法, 這些概念, 最後都在 c++ 裡頭實現了。

而根據 Bjarne 自己的哲學思想, c++ 就是要支援各種不同的風格, function base, object base, oop, generic programming ... c++11 還來個 functional programming。

p6 有張圖, 秀出一些電腦語言的出生時間點, 我很喜歡這張圖, 清楚的說明了程式語言的先後順序, 第一個高階語言是 fortran, 第二個是 lisp。



p23 提到 Cpre (1981 第一個發佈版本), 這是 c with class 的 preprocessor。
p34 說明 this 為什麼是 this 而不是 self (取自 simula), 為什麼是 pointer 而不是 reference (因為這時候還沒有 reference)

c 語言的 f(void), 竟然是被 Bjarne 所遺棄的語法, c++ f() 相當於 c f(void), 這是和 c 不同的地方, 也就是和 c 不相容, 所以 c++ 真的是另外一種語言。

2.7 (p37) 這應該是很多人的疑問, 為什麼是架構在 c 而不是發明一個全新的語言? Bjarne 大約用了兩頁在說明原因, 並提到從 simula, algol68, bcpl 那裡「偷」了什麼特性用到 c++ 中。

2.8.1 介紹了 -> 來代替 *, 這是為了改善 c 的宣告語法, 我的老天, 還好最後 Bjarne 沒這麼做。int v[10]-> 等同於 int *v[10], int f(char)->[10]->(double)-> 對於習慣 c 宣告的我來說, 這似乎也不怎麼高明. 讓 typedef 來對付複雜宣告就好。

c++ 一開始被取名為 C84 (一點都不威阿), 後來 Rick Mascitti 提出了 c++ 這個名字 (真是厲害), 這個名字看來厲害多了。

g++ 1.13 在 198712 公佈, 這是第一個 g++ 版本。

cfront (1984/08 第一個發佈版本) 1986 移植到 pc/at, cfront 並不是 c++ compiler, p59 有張圖可以了解 cfront 的角色, 簡單說透過 cfront 產生的原始碼還要經過 c compiler, 所以才叫 cfront。

c++ 的設計理念之一, 『你只需要為你用的東西付出代價。』用上 class, 和 c 的 struct 一樣, 不會有任何負擔, 加上 virtual function, 呃 ... 這和 c 就不同了, 得付出一點代價。

p77 介紹了對於 operator[]() 這個 operator 重新定義時會有的問題, 以 string 為例子, 引入了輔助用 class 來分別處理左值、右值, 真是巧妙。

2.13, 3.9, 10.7 談到 garbage collection, 尤其是 10.7 花了不少篇幅在討論 garbage collection 所帶來的空間/時間開銷, 這會讓 c++ 威力大減; 只提出了 optional garbage collection 的概念, c++11 似乎要加入這個, 我不相信魚與熊掌能兼得, 希望這功能是可以關閉的, 而不是強迫使用。

3.8
對於 const 這個關鍵字的意義在 c 和 c++ 有所不同, 不過 const 關鍵字可是由 Bjarne 引進到 c 的, 意外吧! 不過當時使用的是 readonly, writeonly 這兩個關鍵字, 最後是由 ANSI C 改用 const 而成為 C 標準, 這是 Bjarne 第一次的標準化經驗 (對象是 c 語言), 不過 c 的 const 無法使用 const expression, 和 c++ 的 const 語意所有不同。

ex:

const int max = 14;
int a[max+1];

在 c 無法這樣使用。別再說 c++ 改變了 c 的 const 用法, 而是 ANSI C 標準委員會沒有採納 Bjarne 的 const 用法。

3.11.1
// 則是由 BCPL 的注解風格, 因為還蠻好用的, Bjarne 就引進了 c++, C99 也支援這個注解風格。

3.11.4 在探討 initialization of global objects, Bjarne 並不是只考慮完成這功能, 還要考慮效率問題, C++ 的設計原則:
"a C++ feature not only has to be usefuel, it also has to be affordable."
這個 c++ 特性不只要有用, 還要能負擔的起 (直翻就是這樣, 完全沒有美感, 請原諒我)。而這個解決辦法會帶來效率上的問題, 所以才說負擔不起。這個方法是 Jerry Schwarz 所提供。現在的 c++ 似乎已經解決這問題 (我不確定)。

g.c
 1 double fabc()
 2 {
 3   return 2.34;
 4 }
 5
 6 double PI = 22/7;
 7 double sq = fabc();
 8
 9 int main(int argc, char *argv[])
10 {
11   return 0;
12 }

看來很平凡的程式, 在 c 中是無法編譯成功的, c++ 則可以, 這便是 initialization of global objects 這問題。

descent@debianlinux:tmp$ gcc g.c
g.c:7:1: error: initializer element is not constant
double sq = fabc();
^
descent@debianlinux:tmp$ g++ g.c
descent@debianlinux:tmp$

我之前遇到類似的問題, 現在證實了這個的確是 c++ 程式, 而不是 c 程式。
http://descent-incoming.blogspot.tw/2012/09/c-c-global-variable-initialize.html

簡單說明一下 (詳情請參考上述連結) c++ compiler 產生這個 (test in g++ 4.8.1):
_Z41__static_initialization_and_destruction_0ii()
幫你完成 sq = fabc(); 動作, 當然也在你不知道的情況下執行了這行程式碼, 所以這麼簡單的一行就要付出一些代價, 程式碼變大變慢, 多了 function call。

c++ 受到 simula, algo68, clu, ada, ml 的影響。

K&R2 (the c programming language 2nd) 裡頭的範例程式碼, 皆可以用 cfront 來編譯, 這是為了測試與 c 的相容性, 畢竟 c++ 還是很重視和 c 的相容性, 但要相容哪些特性那可讓人傷透腦筋。

What Is Object-Oriented Programming?
http://www.stroustrup.com/whatis.pdf
這是 Bjarne 應邀在 ASU (The Association of SIMULA Users) 上做的關於 c++ 報告。

第六章提到標準化的議題, 要將一個語言標準化有很多難題要克服, 也需要有一個清楚的說明, 更要對於好多人的要求有所妥協, 這裡有各國人馬, 有大公司代表, 要搞定這些人, 想想就不容易。

以下幾個程式碼出現於書中, 我好奇的輸入到目前的 c++ 編譯器 (g++ 4.8.1) 中測試:

p124
b.cpp
1 typedef char *T;
2
3 class Y
4 {
5   T f(){T a=0; return a;}
6   typedef int T;
7 };

不會過 ... 是正常的, T 在被使用過之後被重新定義, c++ 不允許這樣的使用。

p126 提到的例子我看不懂, P(Q); Q(P) 還真是怪異。
所以我 po 了這問題, 得到解答:
http://www.ptt.cc/bbs/C_and_CPP/M.1386775835.A.71F.html

int i; 和 int(i); 有著一樣的效果。果然是搞語言標準的人才能提出的問題。

b.cpp
 1 #include <iostream>
 2 #include <typeinfo>
 3 using namespace std;
 4
 5 typedef int P();
 6 typedef int Q();
 7
 8 class X
 9 {
10   static P(Q); // equivalent static int Q()
11   static Q(P); // equivalent to static int Q(int())
12 };

這是 6.3.2 p127 提到的暫時物件生命周期, 我寫了個完整程式並實際測試。

t.cpp
 1 #include <string>
 2 #include <cstdio>
 3 
 4 using namespace std;
 5 
 6 class Mystring
 7 {
 8   public:
 9     friend Mystring operator+(const Mystring&, const Mystring&);
10     Mystring(const char *s)
11     {
12       sprintf(str_, "%s", s);
13     }
14     ~Mystring()
15     {
16       printf("~Mystring: %s\n", str_);
17       str_[0]='1';
18       str_[1]=0x0;
19     }
20     operator const char*()
21     {
22       return str_;
23     }
24   private:
25     char str_[100];
26 };
27 
28 Mystring operator+(const Mystring &s1, const Mystring &s2)
29 {
30   char str[100];
31   sprintf(str,"%s%s", s1.str_, s2.str_);
32   return Mystring(str);
33 }
34 
35 int main(int argc, char *argv[])
36 {
37   Mystring s1("123"), s2("abc"); 
38   const char *p = s1+s2;
39   printf("%s\n", p);
40   return 0;
41 }

和書中所提結果一樣, L38 之後, 暫時物件就解構了, L39 的 p 就不能保證其正確性。讓我吃驚, 我之前沒想過這樣的問題。

6.5 提到兩個沒被接受的特性, keyword argument, restricted pointers, restricted pointers 似乎在 c99 已經提供了 restrict keyword, 用來處理兩段記憶體有無重疊。
ref: C關鍵字 volatile, restrict

6.5.3 character set 提到了很有趣的往事, 說明 # (??=), \(??/) 這些表示法的由來, 我好奇很久了。



這是同一個程式在不同 character set 上的呈獻方式, 就算是 Bjarne 也很難看懂那些丹麥字母在 c 程式裡頭的意思吧?

{, } 在丹麥的字母表上, 會是 æ, å, 這想必令丹麥人頭痛不已, 所以大括弧就變成了 æ, å。

另外一例:
a.c
1 int main(int argc, char *argv[])
2 {
3   if (1 && 2)
4   {
5   }
6   return 0;
7 }

可以寫成這樣:
and 是可以用的, 相信讓不少人訝異。

a.c
1 int main(int argc, char *argv[])
2 {
3   if (1 and 2)
4   {
5   }
6   return 0;
7 }

第七章比較軟, 和 c++ 的推廣/成長有關, The C++ Report, The C++ Journal (很可惜, 已經停刊) 的創刊, Bjarne 自己的書被翻譯了十幾種語言。PC 上的編譯器從 Cfront 的移植到完全是自己開發的商業用的 c++ 編譯器。

c++ 學習上的幾個問題, 是不是先學 c? 需要學 smalltalk 嗎? 擔心使用 c++ 新特徵帶來的效率問題 ... 7.2 (6 頁) 就花了我一小時閱讀 (還是覺得是翻譯的問題)。

[Russo, 1988, A C++ Interpreter for Scheme]
[Russo, 1990, C++ and Operating System Performance]
[Keffer, 1992, Why C++ Will Replace Fortran]
這三篇在討論 c++ 的效率, 還是有人懷疑 c++ 的效率。

沒想到很久之後我也用 c++ 寫了一個 Scheme Interpreter, 可參考: scheme interpreter by c++

有趣的 c++ 使用人數統計, 1979/10 只有一個人使用, 誰呢? 還會有別人嗎?



8.2.1 談到幾個設計 libary 的原則, 我覺得很有參考價值, 列出來給大家欣賞, 簡體中文翻譯實在粗糙, 我還是打英文好了, 並不太難懂。

  • Emphasize run-time efficiency?
  • Minimize recompilation after a change?
  • Maximize portability across platforms?
  • Enable users to extend the basic library?
  • Allow use without source code available?
  • Blend in with existing notations and styles?
  • Be usable from code not written in C++?
  • Be usable by novices?

8.3 提到最早用 c with class 寫出來的 library [Stroustrup, 1980b, a set fo c classes for co-routine]

p 165 提到 iostream 影響了 char 的定義, 我整理了一篇:
c character literal is different from c++

第九章提到對 c++ 的回顧, 從這章可以看出 Bjarne 對 c++ 很有信心, 可以用於大多數的開發環境, 從低階系統程式設計到高階應用程式載到嵌入式系統, 都能用 c++ 來開發, Bjarne 對效率上的謹慎, 對物件導向的支援, 對於泛型的設計, 皆花了不少苦心, 有這樣的自信也能想像, 他應該很得意自己開發的 c++ 語言吧! 我也很喜歡 c++, 不過 2013 競爭對手/開發環境也不同於 1994, c++ 要面對更多的挑戰。

簡體中文版本翻譯的實在很難讀, 大多數章節讀來詰屈聱牙, 也很枯燥乏味 (我認為翻譯要付很大責任, 這本書讀來不應該是這樣), 第一部分就花了我不少時間。看看 2.5.1 的標題, 《純補的實現》, 你一定不知道在談什麼吧? 不過這真的不好翻譯, 因為英文是 simple-minded implementation, 不緊不好理解, 也不知道該翻成什麼字彙, 翻譯真是不簡單。不過該節內容還是可以看懂, 標題《純補的實現》就真的不懂為什麼翻成這樣。

對於 c++ 的技術本書沒太多幫助, 我只是因為喜歡 c++ 所以才看這本書, 真希望侯老師可以翻譯這本, 應該可以縮短我的閱讀時間。

接下來不急著閱讀第二部份, 轉換學習的東西, 看點新技術。

不久後, 我讀完了第二部份:
[books] The Design and Evolution of C++ 第二部分 - 讀後心得

ref:
Errata for The Design and Evolution of C++ (原文版本)
http://www.stroustrup.com/dne_errata.html

The C++ Reference Manual:
http://www.softwarepreservation.com/projects/c_plus_plus/cfront/release_e/doc/ReferenceManual.pdf

C++ in 2005:
http://www.stroustrup.com/DnE2005.pdf

沒有留言:

張貼留言

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

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