|
《C++ Primer Plus》这书就时误入歧途,不知道为啥身边那么多人都喜欢买这本,还各种说网上说这本比Primer基础些,适合新手入门blabla,然后我被问过几次问题,结果都是因为书上写错了……
高票回答总结得很棒了,但毕竟知乎是故事会,还是讲下我的误入歧途。
当初上算法课,之前刚看了《STL源码剖析》,于是我所有的算法代码都写成函数模板,学STL用迭代器表示范围,尽可能用双向迭代器而非直接操作数组下表,看着自己的代码,满脑子的享受。比如这段我完成课后作业的代码- template <typename Iter, typename Pred>
- inline void quick_sort(Iter first, Iter last, Pred pred) {
- using value_type = typename std::iterator_traits<Iter>::value_type;
- // 忽略剩余代码,以及比较迭代器it1和it2指向元素的大小使用pred(*it1, *it2)
- }
复制代码 写其他数据结构也差不多这样,先把框架搭出来,然后沉浸其中美美地欣赏一番。
然而写成这样又如何?- void quick_sort(int a[], int n) {
- // ...
- }
复制代码 写成了C++大牛们嗤之以鼻的一切的原罪的C style?
说到C style,之前体会到了C++的魅力,然后看大牛们说学C++最好的入门方式就是直接学C++不学C。OK,于是拒绝C代码。
后来发现用iostream格式化输出好麻烦啊,还记不住用法,搜一下发现C++的iostream是败笔,性能差还不支持C风格格式化,于是彻底用stdio取而代之,即使是简单地打印一个数也要用printf,看到一个cout就不舒服,看别人代码里出现cout就内心微微一笑:呵,不懂C++的大猪蹄子。
再后来觉得C++的string真烂啊,重载一大堆,实用的没几个,大牛们都这么说,于是vector<char>加STL算法才是处理字符串的正确方式。 当时还第一次知道string不要求底层连续存储,不同于vector ,更加不用string了。(PS:C++11已经规定string底层连续存储了)
再后来看了APUE这种书,又看了大牛们说C++没有针对性的算法,new的性能还不如那些GC语言,于是为了提高性能,又拒绝使用vector<char> ,不过是简单操作个缓冲区嘛,又不是大量数据,需要担心栈空间不足嘛,直接用字符数组代替,你们GC再厉害也不可能比我栈上更快吧。
再后来知道了可重入,觉得一切不可重入的函数都是有害的,坚决不用strerror(errno), 而是用下面这种写法- char buf[100];
- printf(&#34;error: %s\n&#34;, strerror_r(errno, buf, sizeof(buf));
复制代码 然后想这样每个函数内部都得定义一个局部字符数组,会不会多次移动栈指针导致性能开销大呢?以及如果开多线程,或者创建了多个对象,会不会栈空间不够呢?
再后来发现C++的异常安全特别坑爹,比如你在用new创建对象传入智能指针的时候,如果new失败了, 创建的对象也不能被析构。又搜了会发现异常拖性能,虽然有个什么zero-cost model,但是不是所有编译器支持啊,于是错误码就是一切,异常就是垃圾。- // 声明
- std::pair<Object, bool> createObject();
- // 调用
- Object obj;
- {
- auto res = createObject();
- if (res.second) exit(1);
- obj = std::move(res.first);
- }
复制代码 然后发现不对,虽然用大括号避免了检查错误码的res被重复定义(可能后面还要用到类似的函数),但是这里Object必须得有个默认构造函数,如果用在大括号外定义指针而非对象,那么还不如写个Object* createObject()这种C函数,然后检查指针为空呢。虽然我已经抛弃了很多C++的糟粕,但我写的是C++,我不容许C风格的麻烦代码,还要指针&和*麻烦死了,哪有我写的这段代码这么容易阅读?
好像不对。- auto obj_ptr = createObject();
- if (!obj_ptr)
- exit(1);
复制代码 这种代码似乎也行?这种代码一点也不C++权,为了拒绝C的原生指针,我必须再加一行代码- auto& obj = *obj_ptr;
复制代码 补充下正经话吧,最误入歧途的就是过度极端化,C++的自由使得C++更容易极端化。另一方面也源于和C的比较,导致C++er更容易对C抱有优越感,毕竟很多特性是宣称比C好的,标准库也是宣称比C强大的。
但C++就是处于一个尴尬的境地,写底层很多特性没什么用,写高层标准库又太简陋。标准库除了STL容器+算法的部分也都很尴尬。
推崇现代C++是为了利用新版本C++对同样性能的代码下可读性的提升,但这也不意味着是最佳选择,甚至还可能被极端地误解,比如有人说绝对不用宏。
6. 其他 C++ 特性 - Google 开源项目风格指南
这时候就需要一份类似的编码规范,比如看了这里你就知道,说尽可能不用宏,是在哪些具体情景下。假设团队没有规范,也要考虑到团队其他成员的水平,你C++11/14甚至17/20随便用,如果可能导致其他人阅读出现问题,为何不采用简单易懂的写法呢?至于一个人自己的项目,最容易遇到的问题就是提前优化,严格说传值、传指针/引用的问题不在于此,其实规则也很简单(结构体/类的size小于指针的size就传值,大于就传指针/引用)。
举个我的例子,曾经写过小项目,用到了同步队列,当时我直接用C++11的<mutex>和<condition>库包装了 std::queue实现的,后来发现性能有问题。没想着做性能测试,结果花了很久去把同步队列改成无锁的,结果性能一样有问题。后来发现瓶颈根本不在这里……无锁有锁没区别。
我这还算好的了,毕竟当时是写出来才发现性能有问题的,只不过我没有选择去测试而是修改细枝末节的东西。如果是有提前优化习惯的,一开始就浪费时间仔仔细细设计一个无锁同步队列,最后发现瓶颈不在这,于是在编码期就浪费了很多时间,推迟了可以进行测试的时间。
另外,C++太庞大导致学习分不清主次也是误入歧途的表现。最显著的就是模板元编程,这也是大牛们最容易带偏新人的地方,大牛们在模板元相关的回答下各种炫技,很容易让新人觉得好厉害,我也想学。
说句极端的话,模板元编程P用也没。
说句稍微好点的评价,对99.999%的C++程序员而言,学会模板元编程P用也没。(10万人里有1人做模板元编程吗?)
有研究这个的闲情雅致,去看看其他专业知识的实际收益要大得多,毕竟编程语言不是全部,C++更不是全部。 |
|