先把 PPT 放出来,文章以后有空再写吧。
解释:integers 固定长度有什么好处?或者说为什么 <stdint.h> typedefs 没有解决问题?
1. 格式化输入输出 scanf/printf,int64_t 应该用什么格式?"%d" 还是 "%ld" 还是 "%lld" ? int_fast32_t 呢?
C99 为了解决这个,引入了 inttypes.h 头文件,其中定义了一堆宏,类似 PRId32, PRId64, PRIdFAST32,代码写起来是这样:
int64_t value = getValue();
printf("value = " PRId64 "\n", value);
2. 在 C++ 里,可以用函数重载 (overload) 来解决。但是 typedef 并不真正引入新类型(golang 与此不同),你如何知道 int_fast32_t 与 int64_t 是不是同一类型呢?另外还有 size_t/time_t 呢。比如
void foo(uint64_t x)
{}
void foo(size_t x)
{}
在有的系统(64 位 Linux)下会报编译错,因为 size_t 和 uint64_t 都是 unsigned long 类型,不能重载 foo 两次。怎么办?用宏和条件编译吗?
另外的例子是 time_t 和 int64_t:
void bar(time_t y)
{}
void bar(int64_t y)
{}
这段代码有错没错?取决于 time_t 与 int64_t 是不是同样的 typedef,如果整数不定长,除了用丑陋的 #if / #endif 条件编译,有办法解决吗?
解释1:finally 有什么用?确实可以用栈上对象析构函数里的动作来模拟 finally,这又是一个 idiom,为什么不正大光明地让语言支持这一常用功能呢?
解释2:数据成员的默认值有什么用?
如果 class Foo 有一个 enum State state_; 成员,希望初始化为 INVALID_STATE。而 Foo 有 4 个构造函数,那么你得在每个构造函数里写:
Foo::Foo()
: state_(INVALID_STATE)
{}
Foo::Foo(XXXX1)
: state_(INVALID_STATE)
{}
Foo::Foo(XXXX1, YYYY2)
: state_(INVALID_STATE)
{}
Foo::Foo(XXXX1, YYYY2, ZZZZ3)
: state_(INVALID_STATE)
{}
state_ 的初始化要写四处。对于 enum,或许还可以用一个公用的 init() 来初始化。那么对于 class-type 如 string/vector,用 init() 这种办法就不能享受 initialization list 的好处了,因为对象在构造之后再被赋值,重复劳动。
更糟糕的是,万一你将来加了一个 int turnedOn_ 成员,初始值为 -1,你得在 4 个构造函数那里去增加初始化代码,万一漏了一处,等待你的就是 uninitialized value,自求多福吧。
关于 allocator,它没有带来任何好处,如果内存分配这种事情都需要重新定义,重写数据结构也是理所应当的:
http://blog.csdn.net/Solstice/archive/2009/08/02/4401382.aspx
auto_ptr 为什么是坏的,因为太容易用错,且不能放到标准容器里。
Gregory Colvin 最早设计的 auto_ptr 是没有“所有权转移”这个语意的,跟现在的 scoped_ptr 一样。但是标准委员会莫名其妙地加了这个语意,造成了很多陷阱。
scoped_ptr/unique_ptr/shared_ptr 都是更好的替代,语意明确,不容易用错。
至于为什么 valarray 是坏的,见《C++ 标准程序库》相关章节,再说,有谁会用 valarray 做科学计算吗?同样坏的还有 vector<bool>。
如果 XML/logging 这些基本构件不标准化,很难让几个第三方库协作起来,因为每个库都会自己发明一套互不兼容的 logging 和 XML 处理机制。
如果程序里要把 library A 生成的 XML 对象传到 library B 里,恐怕只好用字符串来作为中间媒介,这会增加很多无谓序列化/反序列化的开销。
logging 也是如此,如果没有标准化接口,如何让 library A 和 library B 按相同的格式写到同一个日志文件呢?恐怕又得自己写写 adapter 来协调这些第三方库了。