对于"Why should I have writte

作者: MagicBowen | 来源:发表于2016-06-13 22:04 被阅读700次

    ZeroMQ的作者在文章"Why should I have written ZeroMQ in C, not C++ (part I)""Why should I have written ZeroMQ in C, not C++ (part II)"中列举了他用C++实现ZeroMq时遇到的一些问题,借此批评了C++和面向对象设计。在Part I中他批评了C++的exception机制。对此我觉得没什么好说的,C++本就是一个多范式语言,C++之父说过在C++中你不必为你不使用的特性付出任何代价。如果觉得C++的异常机制在你的开发场景下不能满足要求,想要像C那样靠返回值进行错误处理,那么你只用关闭exception特性(在gcc下增加-fno-exception参数),然后像C那样去处理错误即可。这并不是C++的错,只是你使用它的方式不对而已!我们在嵌入式下一直使用C++,我们的基础设施中有一套断言宏可以让这种靠返回值的错误处理代码更加简洁和优雅,具体参考cub的断言机制。

    在Part II中作者批评了C++ STL库中的list导致了更多的内存分配和内存碎片以及由此引发的性能问题。如下原文中的代码和图示:

    // C++程序的链表
    class person
    {
        int age;
        int weight;
    };
    
    std::list <person*> people;
    
    // C程序的链表
    struct person
    {
        struct person *prev;
        struct person *next;
        int age;
        int weight;
    };
    
    struct
    {
        struct person *first;
        struct person *last;
    } people;
    

    两种链表在内存结构上的差异:

    list.png

    作者把该问题最后推演到是C++和面向对象设计的问题。我想说的是,C++的设计哲学是给你提供强大抽象手段的同时,让你仍然可以在任何特性和性能之间去取舍和平衡。上述问题并不是C++或者面向对象的问题,其实当我们真正掌握了C++的设计哲学,我们完全可以设计出一种既能像面向对象那般地使用,又可以兼顾像C那样内存布局的list,这就是cub提供的List组件。事实上我们已经在嵌入式开发中很愉快地使用该组件很多年了!

    struct Foo : ListElem<Foo>
    {
        Foo(int a) : x(a)
        {
        }
    
        int getValue() const
        {
            return x;
        }
    
    private:
        int x;
    };
    
    TEST(...)
    {
        List<Foo> elems;
    
        ASSERT_TRUE(elems.isEmpty());
        ASSERT_EQ(0, elems.size());
    
        Foo elem1(1), elem2(2), elem3(3);
    
        elems.pushBack(elem1);
        elems.pushBack(elem2);
        elems.pushBack(elem3);
    
        ASSERT_EQ(&elem1, elems.getFirst());
        ASSERT_EQ(&elem3, elems.getLast());
        ASSERT_EQ(3, elems.size());
    
        Foo* first = elems.popFront();
        ASSERT_EQ(1, first->getValue());
        ASSERT_EQ(2, elems.size());
    
        int i = 2;
        LIST_FOREACH(Foo, elem, elems)
        {
            ASSERT_EQ(i++, elem->getValue());
        }
    }
    

    cub中的List是一个双向链表,某一类型T只有继承了ListElem<T>,才可以插入List<T>中去。继承了ListElem<T>的节点内存布局和C习惯中的链表节点内存布局是一样的,链表指针和节点数据是绑定在一起的,而List对象中只包含一个头节点以及统计当前链表大小的变量。List接收节点并完成节点前后链表指针的链接,但是并不拷贝节点,所以对于节点的内存管理完全由程序员决定,List并不做任何假定。

    从上面的例子中可以看到cub的List组件用法和std::list类似,封装了各种接口以及提供了正逆向迭代器,同时还提供了一些方便遍历的辅助宏。关于cub的List的更多用法,具体参考该组件的实现(cub/include/repo/list)和测试源码(cub/test/TestList.cpp)。

    通过上面的例子也可以看出,如果链表指针和节点数据内存在一起,那么哪些类型可以作为链表元素是提前确定好的,而STL库中的std::list却可以指向任何元素,并不需要提前规划。当然有利就有弊,后者确实需要分配更多的内存块。但是这并不是C++或者面向对象的问题,只是STL作为一个基础库,它选择了通用性。如果你需要偏向性能,那么就可以按照自己的使用方式来实现一个list,但是你必须知道你为此放弃了什么。这正是C++的强大之处,它给了你选择的自由,让你可以尽最大的努力去取得更好的平衡。正如cub的List,它兼容了C的内存结构,保证了性能,但谁又能说它的用法不OO呢?

    相关文章

      网友评论

      • _袁英杰_:倒洗澡水连孩子也一块儿倒掉…
      • 刘光聪:@我问了一个错误地问题:smile::smile:
      • 刘光聪:ccinfra的确很强大!
        96989f5f28e9:@MagicBowen ng中的infra里还有具体模块的打印和命名空间定义,通用性不好,不知道最新的版本如何了
        刘光聪:@MagicBowen 我想将magellan对infra的依赖去掉,引用ccinfra,ccinfra开源了吗?:smile:
        MagicBowen:@刘光聪 最近在做lte的c++过程能力提炼,把我们在ng和ce中的基础infra进行了整理,风格一致化,加了测试和文档,形成了ccinfra。后续测试准备改成用magellan写!我们的过程能力提炼,收录了你的好多文章和框架:smile:

      本文标题:对于"Why should I have writte

      本文链接:https://www.haomeiwen.com/subject/wtoydttx.html