分享

C ++ 阴暗面

 ShangShujie 2011-07-13

C++阴暗面

来自博客园-A Lazy Programmer's Footprint 2011-05-20 11:55:00 查看原文

 

近来一篇<The Dark Side Of C++>在 坊间广为转载,作为一个以C++为吃饭家伙的程序员,还是应该下载下来好好读一读的。 总的来讲还是总结的蛮全的,由于个人知识的限制,我读完后将其分为三类:一类是我不以为然的,觉得算不上阴暗面;一类是深有同感,深受其害;而另外一类则 是还不理解,需要日后有时间的时候加以研究的。

一、不以为然

  • 不断变更的标准,迫使我们需要不断更新已有代码。
    作者列出了几点其实影响并不是很大(循环变量的scope;头文件后缀;名字空间)。而且,为了标准的进步,偶尔做出的妥协也是应该的吧。
  • 不断变更的style,作者举得例子是:
        
    Old and busted:
    for (int i = 0; i < n; i++)
    New hotness:
    for (int i(0); i != n; ++i)
      有这个问题吗?有人用第二种方式的吗? 
  • auto_ptr很烂。
    这个,你不用它就是了,而且最近在智能指针加入C++0x大家庭后,应该达成共识了吧
  • iterator可能会失效。
    这个我感觉还好,没遇到过太多问题;
  • iterator对container毫不知情。
    我觉得这是STL设计的一个优点吧,通过iterator解耦算法与容器。
  • vector::at会做边界检查,而operator []不会。
    很好啊,提供两种选择
  • 构造函数与析构函数中的虚函数调用,可能会调用基类的虚函数,甚至是纯虚函数。
    这个不怎么阴暗吧,不要在构造函数与析构函数总调用虚函数应该是个常识,而且,其他语言难道没有这个问题?

二、深有同感

  • 模板中排山倒海式的编译错误,在繁琐无比的错误后面,原因可能仅仅是用错了iterator类型。那个被毙掉的C++ 0x proposal不知是否可以解决此问题。
  • 用C++写的代码不太容易读,函数重载,操作符重载,虚函数重定义,类型重定义,宏定义等等,把代码的真实面貌严严实实的藏在了身后。(当然,这也是为了抽象与一致性),几个例子:
        
    string a("blah"); // 定义一个string对象
    string a(); //声明一个函数

    a
    && b // 如果&&没被重定义,是短路计算;但若是被重载了,那么可能两个都要计算

    typedef OtherType
    & Type;
    Type a
    = b;
    a.value
    = 23; // 不看到那个typedef,鬼知道b的值会不会被改掉

     另外, baz = foo->bar(3);如此简单的一行代码背后蕴含的无穷可能,也充分体现了C++代码难读的特点。
  • 关于cin为什么typecast到void*,而不是bool的讨论,凸显了C++中剑走偏锋的情况 - 火候不到,一招不慎就很容易伤及自身。
  • 析构函数中不应该抛出异常,我以前只知道一个原因 - 就是在前次异常的栈展开过程中调用析构函数并抛出异常,会导致程序退出。但这里给出了我觉得更有说服力的原因:在delete []数组的时候,前面对象析构抛出异常,会导致数组中其他对象内存泄露。
  • 类成员的初始化顺序由其定义的顺序决定,而不是初始化列表中的顺序 - 这点的确引起了较大的迷惑,也带来了不少bug - 因为C++的行为是反直觉的。
  • 函数调用中,传指针的方式比较明显的告诉你该函数可能会改变这个参数,而引用却没这么明显,语法和传值调用一样,却也可以改变参数值。
  • C++过于强大,过于灵活,很多人无法很好的掌控 - 太多复杂的feature set,要用好它,你可以读个博士了~~~
  • prefix ++的重载语法是:operator++(yourtype&), 为了加以区别,postfix的重载语法有个dummy的int参数:operator++(yourtype&, int dummy)。
    虽然我也没有更好的方法,但我承认这的确很傻。
  • 同样的容器,由于使用了不同的allocator就无法交互了,这可以理解,因为STL中allocator是容器类型的一部分,allocator不同导致容器类型不同 - 但这不得不让我们思考STL用这种方式提供allocator是不是合适。
  • map的operator[]自动添加元素,如果不存在的话。
    因为相比于find和insert,operator []实在是太方便了,这个方便的诱惑的确造成了不少麻烦。
  • 模板中你不得不把>>写成> >。因为>>已经被占用了。
  • 用不用exception,如何用好exception实在是个太大太深的话题,都可以在大学开个博士学位了。其中异常安全中resource leak,deadlock是常见的问题。
  • delete []可以很好的处理退化为指针的数组,如果是类的话调用会调用的析构函数s,因为数组元素的个数可以通过sizeof(memoryblock)/sizeof(type)求出。
  • new []可能会引起int的溢出,如: new double [0x8000000] = malloc (8 * 0x80000000),超过了int的表达范围,溢出~
  • 局部静态变量的初始化不是线程安全的 - 这个问题在多线程环境下的单件模式中尤为常见,一般可以用lock解决,但是每次访问都lock比较费力,所以会用一种double-check lock的方式,但是这种方式由于编译器优化引起的reorder,也会线程不安全,需要使用volatile,或者memory barrier防止优化。这个估计可以另外写篇文章了。
  • 用基类指针操作派生类的数组,p++不是指向下一个元素,而是指向了一个不合适的内存地址。
  • 如果你在派生类中有个函数的名字和基类中的函数名字重复,即使函数原型不一样,其基类中的函数都将在派生类中被隐藏。
    这点的确比较过分!背后有什么原因呢? 

三、日后研究

  • 关于名字空间,C++有过什么大的更改么?
    这个估计要查查《C++语言的设计与演化》了
  • 用C++写出好的库基本是不可能的。
    我看到很多人,包括牛人都说过这个,但是不知有没有给过一个列表,C++中那些缺点使其写出好的库成为不可能,哪些语言可以,为什么?
  • 我们不应该在构造函数中抛出异常,因为:Exceptions in constructor don’t unwind the constructor itself。
    这个不太理解,据我所知,在构造函数中抛出异常是构造函数报错的一个方法,因为构造函数本身不返回任何值。
  • 抛出异常时:Does not even clean up local variables!
    不理解,我们的RTTI不就是利用local对象的析构来做内存管理的吗。
  • assert(s[s.size()] == 0); works if s is a const std::string, butis unde?ned if it is not const
    在VC2008上试了一下,没问题。为什么会这么说,为什么? 
  • If you call delete when you should have called delete[], the pointer wilbe off by sizeof(int), leading to heap corruption and possibly code execution.
    不懂。
  • If you call delete[] when you should have called delete, some randomdestructors will be called on garbage data, probably leading to code execution.
    为什么,delete[]会去计算该数组中有几个元素,而答案应该是1,那就不该有问题 - 这个可能和上一点的答案有关。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多