re: C++实用技巧(二) OwnWaterloo 2010-06-24 10:33
@zuhd
刚发现…… 那一天收到的通知太多, 被我直接全部标记为已读, 然后忘了……
通常用同名gmail邮箱, qq不怎么用……
re: C++实用技巧(二) OwnWaterloo 2010-06-24 04:53
>>0指针代表的是没有指向任何有效对象
空指针的二进制表示并不一定是全0 。
浮点数也一样, 0.0f, 0.0, 0.0lf的二进制表示都不一定是全0。
所以, 即使是C语言, 欲使用memset去将指针初始化为空, 或者将浮点初始化为0, 都是不可移植的。
>>30 String& operator=(const String& s)
>>31 {
>>32 delete[] buffer;
>>33 buffer=new char[strlen(s.buffer)+1];
>>34 strcpy(buffer, s.buffer);
>>35 }
这个实现有问题, 当出现自赋值的时候:
String s;
s = s;
this->buffer和s.buffer是同一个指针。
32行delete之后, 已经是dangling pointer。
33行传递给strlen, 34行传递给strcpy都是错误的。
要么判断自赋值的情况:
if (this!=&s)
{
delete[] buffer;
size_t len = strlen(s.buffer)+1;
buffer = new char[ len ];
memcpy(buffer, s.buffer, len );
}
但是, 如果new新的buffer时出现异常, 就会导致this有一个dangling pointer。
为了安全, 可以先new, 再delete:
size_t len = strlen(s.buffer)+1;
char* p = new char[len]; // 之后操作都不会产生异常
memcpy(p, s.buffer, len );
delete[] buffer;
buffer = p;
先new再delete也可以不用判断自赋值的情况了。
re: 怪异的有符号/无符号转换问题 OwnWaterloo 2010-06-13 10:36
@博主
规范。
二元操作符始终会将操作数转换为同一类型计算。
转换规则很复杂, 但有2点:
1. signed T的rank一定比unsigned T要低
2. int 刚好超过默认参数提升的范围
所以 int op unsigned 一定是都转换为unsignd计算。
re: 怪异的有符号/无符号转换问题 OwnWaterloo 2010-06-12 17:45
printf("%d %u\n", ni-ui, ni-ui);
printf("%d %u\n", (ni-ui)*2, (ni-ui)*2);
printf("%d %u\n", (ni-ui)/2, (ni-ui)/2);
printf("%d %u\n", (ni-(int)ui)/2, (ni-(int)ui)/2);
观察输出结果, 明白了吗?
@dudu
>> 一个大家公认的简单清晰的代码规范
异常对于你们不清晰? 怪谁? 怪你没入"技术流"?
并不是大家公认, 使用异常的语言是越来越多, 只是你们不求进步而已。
要说公认, 也就是固步自封流公认罢了。
@那谁
默认构造函数的行为是"可预测"的。
就在你文章第1段中提到的书"inside C++ object model"中就有。
最关键的一句话: "编译器合成的构造函数, 只为满足C++的需要, 而不是程序员的需要。"
所以, 默认构造函数的行为是"可预测的", "确定的", 即 —— 具有trivial construct语意的member, 不会被初始化, 而是包含随机值。
关于随机值的两种处理:
FILE* f;
...
f = fopen( ... );
还是
FILE* f = 0;
...
f = fopen( ... );
后一种所谓的"规范写法", 在我看来完全是多此一举。
当然, 如果你真的需要这种多余的动作:
struct T {
... no constructor
};
T v = T(); // 不再是随机值。 v中的每个值都是确定的。
@陈梓瀚(vczh)
>>但是一个团队使用相同的编译器是应该的
这也就暗示了C++库最方便的复用形式是源代码, 而不是二进制。
>>其他语言的Exception都有Message,这个可是好东西啊……而且catch(...)不能处理基本错误,譬如说access violation,还有divided by zero等等。
其他语言可以 throw 1212么? 1212怎么message?
SEH可以处理那2种, 还可以转化为C++ exception。 但是, 非Windows下?
其他语言是通过"限制能够被throw的对象的类型" —— 比如必须继承自Exception;
来达到使用catch (Exception e)就可以处理所有异常的目的。
C++没有这一限制, 需要自我限制。
其他语言是通过某些平台相关的机制: 比如SEH来处理access violation;
或者是通过损失基础运算的效率: 比如
T& dereference(T* p) { if (p) return *p; throw nullptr_exception(); }
unsigned plus(unsigned x, unsigned y)
{
if (UINT_MAX-x>y) return x+y;
throw overflow_exception();
}
来得到这些错误的异常。
C++不会添加这些保姆工作。
所以, 其实也不算C++ exception相对于其他语言的劣势, 只能算一种权衡。
默认情况下, 不限制可抛出类型, 可以通过catch (...)来处理所有。
如果需要, 可以自我规定一个基类。
默认情况下, 不检测基本运算的错误。
如果需要, 自己检测并抛出。
@陈梓瀚(vczh)
>>1.在我的眼里,throw就如同if,如同for,如同while,如同break,如同continue,如同return,仅仅是一种跳转机制。
而且, 这种跳转机制我认为比goto/longjmp要好理解, 因为它是结构化的。
>>2.市面上所说的throw的危险程度,排除了因为程序员的畏惧而不学习之外,唯一说的过去的无非就是throw很难在ABI上得到兼容。
嗯, exception真正从技术上不能被使用的原因就只有效率和二进制兼容性。
其他都是"人文原因", "市场原因","成本原因", "金钱原因"。
btw: 你发觉C++的二进制兼容性会闯祸了~?
>>4. ... 因为他们心里没谱,没有catch(Exception e)这种万能东西可以用 ...
这倒不难, 敢用exception肯定要人为规定一个最终基类。
即使没有这样的基类, 还可以catch( ... )
re: Python Ogre Blender OwnWaterloo 2010-05-31 17:26
有人曾经专门发系列文章批评Dive into Python。
而且批评的原因中有一点: 陈旧。
自己看着办吧……
python不错吧~_~
这代码, 换个人来也也差不多是这个样子。
程序员有时候就是贱……
抬举他, 给他以选择, 他就乱来了……
贬低他, 给他以束缚, 他就老实了……
@那谁
java程序员也可以说自己"基本用不着指针", "手工内存管理给我带来了很多困扰"。
是否美观并不是我臆断, 这可以使用完成同样功能(当然, 除了效率、以及二进制兼容行上不同)的代码进行对比。 只是cppblog对贴代码并不友好。
所以, 我给出了刘未鹏一篇文章的链接。
里面有完成同样事情, 错误代码如何做 vs 异常如何做 的代码片段。
如果你对异常处理没经验, 错误代码肯定很有经验吧?
1. 即使不能处理, 也必须检测 —— 因为需要向上层报告
2. 这些检测, 重复出现在每个层次, 每个调用点
3. 检测代码与逻辑代码混杂
4. 即使错误最终报告到一个可以处理的层次, 信息早就丢得7788了, 只剩下EINVAL这种含义模糊, 不知道应该如何应对的错误代码。
这些是否是实情?难道你不觉得这些手工的机械重复是不美观的?
而异常处理就是为了解决这些问题。
原始也不是臆断。 理由上面也有: 越来越多的语言开始加入异常机制。
C++很早就加入了, 却被程序员不分青红皂白的拒之门外, 是否原始?
@那谁
我说的是OpenCV和apr的内存管理部分的质量, 请仔细看。
还是那句话: 不学, 什么都困难。
这种所谓的"C++应该避免使用异常"和java程序员所谓的"应该避免使用指针, 所以我们使用java"有什么区别?
"异常的使用导致了程序的走向难以从代码中一目了然的看出来,给问题定位带来困难." —— 而这才是你的偏见。
就你这句话就能推断你没什么使用异常的经验, 而是出于对异常的不理解与恐惧。
请问我说错没有?
@那谁
"我里面提到的,由于引入了异常,导致代码走向难以预测,这一点如何解决?"
你这个问题就像:
由于引入了指针(甚至双指针), 导致代码难以理解,充满bug, 这一点应该如何解决?
难道我们应该放弃指针(和双指针)?
"指针"还可以替换成很多东西。
一句话, 不学, 什么都难以理解。
如何解决?
1. 让代码保持异常安全 —— 无论异常是否发生。
什么游戏都需要规则,这是使用异常的最关键规则。
pthread_cancel有相应的cancellation safety。
信号有相应的可重入。
2. 如果没有异常需要处理, 让异常直接向上报告 —— 无须任何额外代码
而使用状态代码, 即使某个层次不能处理错误, 也必须编写代码去检测, 并向上报告(除非想吞掉这个错误)。
"每一层", "每一个调用点"。
状态代码不优美的地方之一 —— 太多重复代码, 这些代码并不是为了处理这个错误, 仅仅是为了向上报告错误, 所以不得不编写代码去检测。
不优美的地方之二 —— 检测代码和happy path代码混杂在一起, 使得happy path中至少一半代码都是和逻辑无关的, 所谓的噪音。
上面记号系统中说过, 积分记号确实需要学习成本, 但换来的好处就是用最少的噪音表达出尽可能多的信息。
不优美的地方之三 —— caller和callee之间报告错误所使用契约的耦合。
当caller是一个普通的函数这个问题还不突出。当caller是一个函数模板, 对应的callee根本不知道是何物时, 如何定义callee和caller之间的报告错误的契约?
"0表示Ok, 非0表示有问题, 问题写在errno" 而errno这种全局的东西带有全局的一切毛病, 比如:如何分配一个独一无二的code?
3. 如果需要处理当中的部分异常, try之, 并仅仅catch感兴趣的异常, 处理之。
接上面的, 如果不能得到一个独一无二的code, 就无法知道究竟是出了什么毛病。
EINVAL? 哪一个参数有问题? 什么问题? 信息早就丢了。
无法有针对性的进行处理。
更多的, 请看:
http://blog.csdn.net/pongba/archive/2007/10/08/1815742.aspx而google coding style中所谓的不使用exception的理由, 在知道什么是异常处理的人的眼中, 只有最后2点是站得住脚的:
最后一点, 也就是上一个回复说的"与其规定什么时候可以用, 应该如何用", 不如直接"全面禁止" —— 因噎废食。
倒数第2点, 就是效率问题。 在"效率攸关"的地方, 确实不能用。
但这种地方并不是想象中的那么多。
其他都是扯谈。 要么写这个规范的人不懂exception, 要么就是他懂, 但故意牵强附会, 或者危言耸听。
另外, 这种高压政策下的google的代码我没看过, 所以无法评论其质量高低。
但肯定是"原始且不美观"的。
而这种在高压政策下产生出的"原始且不美观"的其他代码我倒是看过, 例如OpenCV和apr中的内存管理部分 —— 在这种"泯灭人类的思维与创造性, 仅仅将人作为制造code的工具"的高压政策下压榨出的代码, 质量怎么可能高?
@那谁
一说就起劲了……
再说说操作符重载。 equals add vs ==, +
记号系统的严谨与简洁都很重要。
以前我也只注重记号系统的严谨, 认为严谨的就是易读、易理解的。
直到读了这篇文章:
http://www.ibm.com/developerworks/cn/xml/x-matters/part24/index.html清单3中的xml就是严谨而不简洁的。 即使不学习任何新事物, 也大致能猜出清单3表示的是什么内容。
而清单2的记号系统就是简洁的, 同时也是严谨的 —— 只是需要学习这套记号系统。
如果不学习清单2中的记号系统, 那清单2可能就是天书。
如果学习了, 那清单2就几乎是"用极少废话作辅助, 表达作者的意思"。
而清单3,无论是否学习, 都是很罗嗦的在表达作者的含义, 包含了太多的噪音。
我觉得(只是觉得, 因为历史不能重演), 如果微积分的记号系统不是随微积分一起产生, 微积分是发展不起来的。
将表达式中的Sx 全部替换成 interal(x); 记号是不需要学了, 但这表达式根本没法读。
所以, 不是简洁的记号系统不可取, 而是记号系统在简洁的同时还要保持直观, 严谨, 无歧义。
要设计出这样的记号系统是需要精心的、 全局的、 周详的准备。
而C++的操作符重载让记号的引入变得太容易, 获得简洁的同时容易丢失其他方面的因素。
上面回复已经说到google中C++程序员的素质。
与其于让他们理解直观、严谨、无歧义, 直接让他们不许使用新的记号系统可能更省事。
但无论如何, 这所谓的"高标准"的规范, 其实只是"无奈"之举, 而非"进步"。
@那谁
"C++某特性不理解,产生畏错心理(或者已经得到教训);
对此采取的措施不是去学习与理解这些特性, 而是放弃这些特性。"
对筷子不熟悉,不尝试学习与熟练,而是放弃 —— 这也不是不行, 毕竟用刀叉(甚至手抓)也算是吃饭 —— 但是, 如果是中餐的话, 这并不是什么值得为之感到光荣的事。
吃饭不同于软件开发的地方是:
餐桌上的同伴吃中餐不用筷子最多最多让人感到难堪。
而项目中的队员如果做不到步调一致, 就可能导致项目失败。
但是, 相比这饭吃得是否得体, 步调一致更为重要。
所以, 为了那些因噎废食的人, 不得不同他们一起"吃手抓饭", 从而达到步调一致, 也是很无奈的事。
—— 这依然不是什么值得炫耀的事情。
在正常控制流中插入的另一条控制流是异常的关键。
正是这条非寻常的控制流, 导致学习成本。
如果愿意去学习这条控制流的规则, 就能得到这条控制流带来的好处:
它确实将“错误的检测与处理”给分开了。
—— 中餐是否值得吃,就是在学习成本与所有层次上的所有错误都必须“手工地”检测并向上报告(即使不处理任何错误)之前做选择。
越来越多的语言加入异常处理机制(甚至是一些原本没有的语言)。
并且, 在正常控制流之外的控制流并不只在异常处理中出现: 信号, thread_cancel都有,
要么“永远逃避所有使用非正常控制流的技术”。
—— 永远不去学习如何使用筷子。
要么去学习任意一种, 并举一反三。
—— 学习筷子的使用后, 品尝中餐的诸多菜系都有基础了。
关于google, 我感到困惑的是: 对google中的python和java程序员是否也有不许使用异常的规定。
我想应该是没有的。
那么, 换来的结论就是: goolge中的C++程序员, 对异常这种编程思想的理解, 还不如google中的python和java程序员。
re: SASL 的 Name Mangling OwnWaterloo 2010-05-25 00:34
@空明流转
哦, 这样……
为什么要新造一门语言呢? —— 如果这个问题过于无聊可以无视……
我只是想收集一下创造一门语言的n个理由……
谢谢……
re: SASL 的 Name Mangling OwnWaterloo 2010-05-24 23:59
SASL是什么? google了一下, 好像都不是……
re: 一种线程安全的单例模式实现方式 OwnWaterloo 2010-05-22 19:09
@匿名
知道什么叫"举一反三"吗?
re: 一种线程安全的单例模式实现方式 OwnWaterloo 2010-05-22 19:08
@OnTheWay
注意两种需要运行时初始化的静态对象, s和s_。
前者的初始化时机C++有保证, 但不保证多线程安全。
后者的初始化时机C++只保证同一翻译单元内中有顺序。
再看static CAssistForSingleton m_refSycObj;
这就属于第2种。
下面的情况有发生的可能性:
1. 另一翻译单元的静态对象先于m_refSycObj被初始化
2. 在它初始化时访问了CSingleton *GetInstatnce()
此时就访问了一个"未初始化"的临界区。
这已经是bug。
再有, 如果有下列情况:
1. 一些先于m_refSycObj初始化的代码开启了线程
2. 多个线程在m_refSycObj初始化前访问CSingleton *GetInstatnce()
一个未初始化的m_refSycObj根本不能用于同步。
所以, 上面的第1个问题: 这样做不是多线程安全的。
而静态对象在构造时启动线程的情况并不多, 所以并不一定需要将s_作成多线程安全。
例如boost就是这样, 要求在进入main之前, 是不许有两条以上的执行路径去访问。
这就是第2个问题。
如果需要完全的多线程安全:
1. 使用once_initial函数(pthread或者win6有提供)
2. 使用"可以静态初始化"的锁, 比如pthread_mutex_t就可以
或者自己使用一个spinlock也行。
我没有msn…… 用gmail的同名邮箱。
re: 一种线程安全的单例模式实现方式 OwnWaterloo 2010-05-22 09:00
@OnTheWay
再来一种:
class S
{
// ctor, copy, dtor, assignment
static S s_;
public:
S& instance() { return s_; }
};
S S::s_;
同样是上面的问题。
re: 一种线程安全的单例模式实现方式 OwnWaterloo 2010-05-22 08:57
@OnTheWay
class S
{
S() { ... }
~S(); { ... }
S(S const&);
S& operator=(S const&S);
public:
static S& instance()
{
static S s;
return s;
}
};
你认为C++(C++03)是否保证S::instance是线程安全的?
如果是, 请说明理由。
如果C++不保证, 是否应该将S::instance作成线程安全的?
re: It's never too late OwnWaterloo 2010-05-21 15:48
矫情-_-
re: 一种线程安全的单例模式实现方式 OwnWaterloo 2010-05-21 15:37
依然不是线程安全的。
re: 高质量c/c++编程读书笔记(1) OwnWaterloo 2010-04-09 17:41
又见这本垃圾书。
除了第1条, 其他全是狗屁。
re: 对 C++ 历史的个人观点 OwnWaterloo 2010-04-09 16:58
@陈硕
MFC、QT、ACE太老是吧? 来个新点的?
http://www.libnui.net/去看看它是什么时候开始开发的, 又重复发明了多少轮子吧。
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-09 15:09
@溪流
你这个问题很好解决~
找个现代点的ide...
re: 对 C++ 历史的个人观点 OwnWaterloo 2010-04-07 21:25
@唐风
TL上跑题太严重, 看吧, 没几楼就扯到profiling, 扯到“大部分应用都合适了”。
如果万事都扯这么多无关的东西, 就没什么好讨论了。
allocator的问题不在于是否应该有allocator参数,是否应该将allocator作为模板参数。
上面已经说了:
1. 如果一个container没有allocator参数
那它内部的分配方式必然不能被定制, 它绑定了一个“策略”。
当这种策略无法满足用户要求时, 整个container的实现连同这个策略一起被抛弃。
2. 模板vs非模板参数
上面也说了, 模板是非模板的一个范化形式。
模板可以得到非模板的东西, 只要实例化一次即可。
反之不行。
当template alias加入语言后, 使用模板的设计会更优雅。
allocator真正的问题是标准对allocator的状态的描述很模糊。
如果同一allocator type的所有instance都可以被认为是相同的 —— 也就是说,A和B是同一个个类型的2个instance, 从A分配的内存可以由B释放 —— 那没问题, 可以安全的和STL配合使用。
但通常, allocator都需要per instance的状态才能发挥真正作用。
而这种带有per instance状态的allocator和STL交互, 标准说得很模糊。
如果标准规定STL的实现必须注意这个问题, 那allocator就非常好用。
绝对不存在lz所说的“基本用不上”。
即使就是现在的情况, 也可以用一些方式绕过去, 只是很不美观。
综上, allocator的设计应该被改进(取消这个限制), 而且依然作为一个模板参数。
btw:如果C++0x真引入了template alias,boost的智能指针就是一坨垃圾, 无论它设计多少个, 在Loki面前都是垃圾。
re: 对 C++ 历史的个人观点 OwnWaterloo 2010-04-07 16:37
@wuqq
C++不玩那套, 别拿其他语言的什么接口/实现那套玩意来“套”C++的设计。
allocator是一种规范。
整个STL都是在这种duck typing的基础上构建起来的, 而不是java或者C#那种接口式的设计。
如果你确实需要按接口的方式来使用, 自己实现一个满足allocator规范的接口, 假设叫IA。
然后使用vector<T,IA> 即可。
无论你使用什么IA的实现, 你都可以使用vector<T,IA>类型。
甚至可以使用0x中的新的特性:
template<typename T>
using your_vector = std::vector<T,your_allocator<T>>;
your_allocator是一个模板, 转发到IA上。
然后就一直使用your_vector就行了。
模板和接口就是在效率与代码体积上的权衡。
但是, 用模板实现的代码, 如果在乎代码体积, 可以轻易转换为接口去使用。
并且获得接口的所有好处, 例如不暴露实现。
反之, 如果一开始就用接口,当需要模板的行为时, 就没得搞, 永远没办法, 除了重写。
vector太麻烦, 换个例子。
如果将排序算法使用模板实现:
template<class RanIt, class Cmp>
void sort(RanIt first, RanIt last, Cmp c);
那么, 这个模板可以轻易转换为C语言中使用void*和int (*cmp)( ... )的qsort, 也可以轻易转换为使用接口作为Cmp和RanIt的sort。
同时, 不暴露这个模板的任何实现。
你可以将这个模板隐藏到实现文件中。
反之, 如果sort一开始就是qsort或者使用接口:
当你确实需要效率,确实针对不同类型生成另一套代码的时候, 你必须重写。
这就是模板的强大之处, 它的使用是很灵活的, 看你的需求而定。
你不仅仅可以“直接使用”, 你还可以将它作为“代码生成器”, 生成满足你需要的东西。
re: 对 C++ 历史的个人观点 OwnWaterloo 2010-04-07 01:58
———— integers
>> integers 固定长度有什么好处?或者说为什么 <stdint.h> typedefs 没有解决问题?
scanf/printf这个你已经说了, inttypes.h
而cin/cout更不用说了, 因为有重载。
>> 在 C++ 里,可以用函数重载 (overload) 来解决。但是 typedef 并不真正引入新类型(golang 与此不同),你如何知道 int_fast32_t 与 int64_t 是不是同一类型呢?另外还有 size_t/time_t 呢
你真的需要吗? 真的需要有is_same_type这种东西。
你通常你需要做的就是像cin/cout那样, “对类型本身, 而不是各种typedef”去重载。
比如:
void f(char x); void f(int x); ...
然后无论uint64_t和size_t是什么, 都可以f之。
或者使用enable_if这种机制。
java和C#那套东西, 只是在当前的pc机已经服务器上行得通。
比如它们要规定整数长度, 规定浮点规格, 甚至连char都规定了。
但这是很短视的作法。
这两门语言所说的unicode, 其实只是utf16。
我不知道usc4流行起来的时候, 这两门语言打算怎么办?
又搞出个wchar?
或许那时候它们早就灭亡了。
而对其他各种古怪的平台, 我也不知道java se和.net compact活得怎样。
我只想知道, 在16位的平台上, 32位的int是否总是要由2个机器字拼出来。
甚至, 在8位平台上, 是否需要4个机器字来拼。
而这两门语言又希望做到强类型, short的使用不是那么愉快:
short s = ...;
s = s + 1; // error
s += 1; // ok
C语言肯定是打算尽可能贴近机器的, 而不是贴近程序员的。
所以C标准不会规定确定长度的整数, 即使C99中, 那些typedefs也是optional的。
而C++, 这个就有分歧了。
我是希望C++依然能贴近机器。
毕竟, 如果真的需要开发起来很爽, 已经有java或者C#了, 直接用就是了。
如果让C++规定整数长度, 而提供:
typedef xxx nature_width_t;
并大量使用之, 我觉得很难接受。
———— finally
>> finally 有什么用?确实可以用栈上对象析构函数里的动作来模拟 finally,这又是一个 idiom,为什么不正大光明地让语言支持这一常用功能呢?
这一功能常用吗? 在我看来, 它只是没有确定性析构时机的语言中的一种“补救措施”。
不能因为其他语言有这个, 就一定要搬到C++中。
C++不需要这种烂玩意, 请允许我说它是烂玩意。
为什么? 因为finally会造成“执行代码与回滚代码”分离两处 —— 相信你明白我的意思。
所以C#会早早引入using, 而不思进取的java也终于打算在java7加入类似的机制。
所以, 这种东西只会让代码写得更烂, 而不是更好。
这是最主要的原因。
其次, “让资源归类所有”, 这在C++中已经被广泛接受了, 是这样吗?
所以, 在很多时候, C++都可以将action和rollback写在一处。
而实现一个范型的RRID类, 其实Loki已经有ScopeGuard而boost也有scope_exit。
当语言支持auto和lambda之后, 它们都不再需要使用语言的阴暗角落或者晦涩的语法。
可以实现的十分优雅, 用户代码也可以写的十分优雅。
而且auto的实现并不难, 编译器必然是知道某个表达式的类型。
lambda应该也不难, 很多时候那些手写的functor都是有规律的, 很适合让机器自动生成的。
而finally的实现方式, 我就不清楚了。
———— 默认值
>>解释2:数据成员的默认值有什么用?
我依然没看出有什么用。
你举的例子, 其实是C++0x打算支持的“转发构造函数”可以优美解决的。
而且, 据我所知, C++0x也是打算支持这样的特性的, 我忘记叫什么名字了:
class X {
T v = default_value_for_v;
};
注意, 我的观点不是排斥这种特性。
我反对的是将这种行为“作为默认行为”。
我希望默认情况下, 语言不要做多余的事情, 除非我显示要求。
—— allocator
我看了你给的链接, 没发现有具有说服力的例子。
1. 当你不需要定制allocator。
无论那种设计, 都不会对你造成影响。
2. 当你需要定制allocator。
无论那种设计, 你可能都需要将两种容器区别对待。
也就是说, 你这种要求:
>>但这完全没道理,我不过想访问一个vector<string>,根本不关心它的内存是怎么分配的,却被什么鬼东西allocator挡在了门外。
不过想访问一个vector<string>的要求, 本来就是不合理的。
你只考虑到了“使用allocator会有什么坏处”, 却没想过“不使用allocator你同样会遇到这些问题”。
所以, 这并不是allocator的错。
而使用allocator的设计, 你至少不需要去重新实现一套数据结构。
当然, STL对allocator的设计并不是很好。
比如它要求不同的allocator必须被认为是同一个。
所以会导致这样的问题:
>> 那么每种类型的allocator必须是全局唯一的(Singleton)
C++应该取消这个限制, 而不是取消allocator。
在你所说parser的case中, 你的目标是这样对吗:
一个allocator的类型有不同的实例, 这些实例是不在线程之间共享的。
那么, 这些container本身也是不在线程间共享, 依然可以用allocator解决。
只是它会触犯STL对allocator的限制。
—— 各种库, auto_ptr, valarray, xml, log
>>auto_ptr 为什么是坏的,因为太容易用错,且不能放到标准容器里。
C++什么东西是“不容易用错的”?
(而且, 当你允许工程使用boost之后, 你会遇到更多容易用错的东西)
这和finally还不同。
学会finally之后, 写出的代码依然是臭的。
而auto_ptr, 至少在学会之后, 它还是有用的。
valarray的slice我还确实没用过。
对, auto_ptr, valarray, vector<bool> 都是有各种各样的毛病的。
但是, 既然他们已经被加入标准库了, 对付这些毛病的办法就是去“学”。
移除是没法移除的。 早就被deprecated的strstream到现在都可以使用。
vc10, gcc4.4.0), vc10甚至一个警告都不给。
而且, 它们(包括strstream)对会使用的人来说并不是一无是处。
就我来说, 我并不需要scoped_ptr, 或者说不会因为这样一个小东西去引入boost,或者将编译器限制到支持C++0x的那些上。
vector<bool> 和valarray都是试验品。
如果你需要一个可增长的位图, 你还是得去实现vector<bool>那样的东西。
使用上还是需要注意将他们和STL其他容器有所区分。
那么, 移除它能获得什么好处?
valarray就让它呆那里就可以了, 会给你带来负担吗?
—— xml和log
你说的这个问题, 是不能靠语言提供标准库来做的。
很简单, Qt用std::string了吗? ACE用了吗? MFC用了吗?
你可以说std::string设计得差, 设计得太学术。
你也可以说上面那些库不屑于使用模板。
但是, std::xml, std::log难道不会重蹈覆辙?
上面讨论的那几个家伙(auto_ptr, valarray, vector<bool>)已经犯错了。
同时, 正因为我们看到这些不够完美的东西, 对待标准库时我们更需要“谨慎”。
我觉得标准委员会做得很好, 甚至将concepts砍了都做得很好。
就是两个字“谨慎”。
没有它们, 依然还在开发; 但如果因为冲动加入它们, 可能就是以后无法移除的负担。
就像上面那几个。
目前这种模式: 将boost作为试验田, 等有充分使用基础之后再纳入tr1, 我也觉得很好。
C++需要的是稳定, 只把必须加入语言的东西加入,(移除就更没必要了) 其他的慢慢观望。
我宁愿使用一个从vc8-vc10, 从gcc3-gcc4都可以使用的C++。
而不是告诉用户, 你必须使用什么编译器的什么版本。
C++不像C#那样, 可以霸气的说“我就是不向后兼容,你拿我怎么招?” “你不还是得用我?”
re: 对 C++ 历史的个人观点 OwnWaterloo 2010-04-06 22:38
个人觉得很多改进都是不切实际的。
—— Exact-width integer types
没有必要。
当需要确定宽度的整数类型时, 应该使用stdint.h那样的typedef。(据说C++0x也会引入相应的cstdint)
而不是一开始就将数据类型固定死。
而且,即使是C99, Exact-width integer types也是optional的。
也就是说,确实是不能保证在每个平台上都有确定宽度整数。
更有用的应该是Minimum-width integer types
或者Fastest minimum-width integer types
—— finally
引入finally还不如引入lambda和auto。
有了lambda和auto, 配合RAII, finally就不值钱了。
而lambda和auto也是C++0x准备加入的。
—— default value
这能有什么用?
C和C++给程序员最大限度的控制。
struct X x;
X_fill(&x); // 明明知道x马上就会被fill, 初始化也是多余的。
如果确实需要:
struct X x_default_initialized = X();
—— allocator
绝对是需要的。 你用不上不等于别人用不上。
如果去掉allocator, 当那个默认的allocator不满足你的需要时,报销的(不能被复用的)不仅仅是那个默认的allocator, 而是连同整个data structure库都废掉。
而加上allocator并不会引起什么害处。
别说“vector<int, my_allocator> 和vector<int> 不是同一类型”什么的。
当你确实需要这种行为时, 他们本来就不应该是一个类型。
—— auto_ptr和valarray
不知道这两位又怎么了。
是不是boost::scope_ptr比 const std::auto_ptr新潮?
而在C++加入restrict 之前, valarray都是有用的, 只是你可能用不上而已。
—— 其他的库
只有threading是必须加入语言的。
因为语言不提供帮助的话, threadding库是搞不出来的。
而其他的network, xml, log,就越界了。
C++不是那些有大公司撑腰的语言, 可以对标准库肆意的扩充。
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-06 17:14
@溪流
跟"最小接口"与"人本接口"没什么关系。
相对于read_int, read_string, 这两种设计都可以算是最小接口。
为了不降低"实现优化的潜力", 保留适当的信息对实现是有用的。
比如malloc vs calloc。
用户端的信息原本是 sizeof(elem) 和 count两个。
calloc会将信息继续带到下层。
而malloc将这个信息丢掉了。
这可能就会影响malloc的某种优化, 比如对齐策略。
1. 分配12个char的数组
2. 分配12/sizeof(int)个数组
对malloc来说, 它接受到的都是12bytes, 已经无法区分这两者的不同。
malloc始终返回的是该平台下有最严格对齐的内存。
而calloc就可以明确知道每个元素的大小, 可以利用这个信息, 计算出该种大小的所有类型在该平台下的最严格对齐需求,就可以少填充一些。
当然, calloc不一定会使用这个信息进行优化, 但它保有这个优化的潜力。
(calloc另一个memset的行为就很恶心了)
而read, 没想出可以利用size和count进行优化的方法。
而且将这2个参数分开, 同时又要避免返回时那个除法的话, 还会显得不够统一。
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-06 16:59
@溪流
对, 你说得对。
fread确实干了多余的事情。
调用时那个乘法无所谓, 要么就是调用者自己做, 要么就是fread做。
问题出在返回时那个除法上。 fread总会做一个除法, 对很多情况是不需要的。
而不使用乘/除法实现的fread又需要大量的循环。
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-05 23:54
@溪流
>>那应该告诉我读了 sizeof(T)/2 个字节,而不是告诉我读了 0 个 T 啊
你已经形成思维定势了:
1. fread应该设计成什么样子, 你心中已经有答案
2. fread如果设计成那样子我会怎么用, 你已经习惯这么用
3. 应为我习惯这么用, 所以fread设计成这样就是不对的
这是循环论证, 说明不了任何问题。
现在你要抛弃你的习惯:
习惯1: 不仅仅用来读取字节流, 还可以直接读取二进制格式文件
习惯2: 有返回值检测
然后比较两段代码, 看哪段写着顺手:
void f(T* buf, size_t buf_size, FILE* stream)
{
size_t count = fread(buf, sizeof *buf, buf_size, stream);
for (size_t i=0; i<count; ++i)
process(buf[i ] );
}
void g(T* buf, size_t buf_size, FILE* stream)
{
size_t bytes = Read(buf, sizeof(T) * buf_size, stream);
for (size_t i=0; i< bytes/ sizoef(T); ++i)
process( buf[i ] );
}
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-05 22:05
@溪流
读出半个T怎么办?
还没看懂2楼的代码?
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-05 21:08
@溪流
你不需要不等于别人不需要……
你完全将ReadFile或者fread当作"字节流读取"来使用了。
实际上,fread不仅仅可以用来读取字节流;
还可以用来读取"二进制格式存放的数据", 比如上面的T。
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-05 18:58
@溪流
2楼仔细看。 我懒得说了。
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-05 18:36
@溪流
你想得太狭隘了。
仔细看2楼的例子。 T非得是char吗?
a非得是数组吗?
T a[1]; 可以吗?
T v;可以吗?
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-05 17:17
@溪流
凭什么只能是unsigned char*呢?
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-05 15:40
@溪流
呃。。。 下面不是还要做一次除法么。。。
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-05 01:00
@溪流
下面的READ就是按你说的方式设计的。
你觉得用着方便吗?
re: fread、fwrite 的参数设计问题 OwnWaterloo 2010-04-05 00:45
fread和fwrite是C标准库, Read/WriteFile是Windows API。
如果不需要HANLDE的特殊功能, 使用fread/fwrite的代码可以不加修改的移植到其他平台去。
参数用于错误处理。
T a[count];
if ( count != fread(a, sizeof a[0], count, stream) ) {
// error
}
if ( count != READ(a, sizeof a, stream )/sizeof a[0] ) {
// error
}
msvc和gcc都有相应的分析工具。
VS的IDE中有个性能分析工具, 具体是怎么让cl去完成的不了解。
而gcc相应的工具是gprof和gcov。 gprof是使用-pg选项,给每个函数添加hook代码(它是编译器,当然知道如何添加。。。)
gcov的选项有点复杂…… 好像是-ftest-coverge什么的。
只要截获每次函数调用, 除非是搞什么longjmp或者exit, 调用树运行时间等东西都可以得到。
当然, 这不太像log了…… 不过看你的描述, 你需要的功能也不是log了……
就是程序动态分析工具。
re: 讨论:单件模式的优点何在?有无存在的必要? OwnWaterloo 2010-04-03 19:51
@cexer
在我看来恰恰相反, 全局数据与用户之间的交互形式有若干, 而单件只是其中一种。
re: 将成员函数作为回调函数 OwnWaterloo 2010-04-03 19:49
@Sanxcoo
哈, 既然你已经找了这么复杂的方案, 相信你是清楚tr1::function这种东西是无效的~_~
tr1::function对象或者std::binder1st, std::bind2nder对象的接受者都是template。
而当接收者只能接受pointer to function时, tr1::function, binder这种东西都是无效的~
这里有一个类似的, 不过不需要写汇编。
http://code.google.com/p/callback-currying/source/browse/trunk/src/stdcall2stdcall.c项目新的地址在
http://github.com/OwnWaterloo/callback-currying不过目前是空的……
我也这么干过:
class C{
C() {
CoInit()
}
~C() {
CoUnit()
}
...
};
为的也是让其他代码干净一些, 不要引入那些丑陋的头文件以及看到COM这么丑陋的东西。
全局数据初始化有不同的设计方法。
如果内部有引用技术, 就可以这么干。
如果没有, 就告诉C的使用者, 使用前需要怎样, 使用后又怎样。
@溪流
MIT应该不用, 只保原始代码中的版权申明即可。
这话说的很不负责啊, 最好还是详细查一下……
re: 将成员函数作为回调函数 OwnWaterloo 2010-04-02 15:56
@陈梓瀚(vczh)
std::function?
那是什么东西?
re: 讨论:单件模式的优点何在?有无存在的必要? OwnWaterloo 2010-04-02 15:54
@cexer
用于单件的那些技术, 同样可以用于全局数据初始化。
C运行库为你初始化堆分配器, 不也不需要你多余控制?
进程终结后也会自己终结。
呃, C库不一定会真正释放, 有可能留给OS。
那换个例子, atexit。
所以单件根本不是什么伟大的发明。
只是OO时代的顺应产物。
re: loki技法(1).静态断言 OwnWaterloo 2010-04-01 22:57
《modern c++ design》 是loki作者写的介绍该库的书。