反思C++面向对象与虚函数
- C++语言学习可以看《C++ Primer》这本书;
- 在C++中进行面向对象编程会遇到其他语言中不存在的问题, 其本质原因是C++ class是值语义, 而非对象语义;
朴实的C++设计
- 实用当头, 朴实为贵, 好用才是王道;
- C++ 是一门(最)复杂的编程语言, 语言虽复杂, 不代表一定要用复杂的方式来使用它;
- 不一定非得有基类和派生类的设计才是好设计;
- 一个类代表一个概念;
- 让代码保持清晰, 给我们带来了显而易见的好处;
- 不要因为某个技术流行而去用它, 除非它确实能降低程序的复杂度;
- 在C++这样需要自己管理内存和对象生命期的语言里, 大规模使用面向对象、继承、多态多是自讨苦吃;
C++编译器ABI的主要内容主要包括几个方面:
- 函数传递的方式, 比如x86-64用寄存器来传函数的前4个整数参数;
- 虚函数的回调方式, 通常是vptr/vtbl机制, 然后用vtbl[offset]来调用;
- struct和class的内存布局, 通过偏移量来访问数据成员;
- name mangling;
- RTTI 和异常处理的实现;
- 避免使用虚函数作为库的接口;
- JAVA的库都是.jar文件;
虚函数作为库的接口的两大用途
- 调用;
- 回调, 也就是事件通知, 比如网络库的连接建立, 数据到达, 连接断开等;
- 混合使用;
iostream的用途与局限
- C++ iostream的主要作用是让初学者有一个方便的命令行输入输出试验环境, 在真实的项目中很少用到iostream;
- 不用花大量的精力在iostream的格式化与manipulator(格式操作符)上;
- glibc定义的getline函数来读取不定长的行;
- 返回的是malloc()分配的内存, 要求调用端自己free()掉;
- iostream不是线程安全的;
- 用到了多重继承和虚拟继承;
- Google Protobuf是一种高效的网络传输格式, 它用一种协议描述语言来定义消息格式, 并且自动生成序列花代码;
- C++的强大之处在于抽象不以性能损失为代价;
- 数据抽象(data abstraction)是与面向面向对象(object-oriented)并列的一种编程范式(programming pragadigm);
数据抽象所需的语言设施
- 支持数据聚合(data aggregation);
- 全局函数与重载;
- 成员函数与private数据;
- 拷贝控制(copy control);
- C++ class是值语义, copy control是实现深拷贝的必要手段, 而且ADT用到的资源只涉及动态分配的内存, 所以深拷贝是可行的;
- 操作符重载;
- 效率无损, 抽象不代表低效;
- 模板与泛型;
- 数据抽象是C++的重要抽象手段, 适合封装数据, 它的语义简单, 容易使用;
- 大多数class都是对象语义;
C++经验谈
- 练从难处练, 用从易处用;
- 软件开发一定要时刻注意减少不必要的复杂度;
- 作为应用程序的开发者, 对技术的运用要明智, 不要为了了解难度系数为10的问题而去强攻难度系数为100的问题, 这就本末倒置了;
- 用异或来交换变量是错误的;
- 未定义的行为, 在C/C++语言的一条语句中, 一个变量的值只允许改变一次(像x = x++这种代码都是未定义行为, 因为x有两次写入);
- 现在的编译器会把std::reverse()这种简单函数自动内联展开, 生成出来的优化汇编代码和其他代码一样快;
- 不要猜(guess), 要测(benchmark);
- 不要重载全局::operator new();
- 按现代C++的手法(RAII)来管理内存, 很难遇到什么内存方面的错误;
- 内存管理的基本要求:
- 内存管理的基本要求是不重不漏 -- 既不重复delete, 也不漏掉delete;
- new/delete配对, 不仅是个数相等, 还隐含了new和delete的调用本身要匹配, 不要东家借的东西西家还;
- 用系统默认的malloc()分配的内存要交给系统默认的free()去释放;
- 用系统默认的new表达式创建的对象要交给系统默认的delete表达式去析构并释放;
- 用系统默认的new[]表达式创建的对象要交给系统默认的delete[]表达式去析构并释放;
- 用系统默认的::operator new()分配的内存要交给系统默认的::operator delete()去释放;
- 用placement new创建的对象要用placement delete(为了表述方便, 姑且这么说吧)去析构(其实就是直接调用析构函数);
- 从某个内存池A分配的内存要还给这个内存池;
- 如果定制new/delete, 那么要按规矩来;
- 检查代码中的内存错误;
- 优化性能;
- 获得内存使用的统计数据;
- 脚本语言解释器代码:
- C语言的static关键字的两种用法:
- 用于函数内部修饰变量, 即函数内的静态变量; 使用静态变量的函数一般是不可重入的, 也不是线程安全的;
- 用在文件级别(函数体之外), 修饰变量或函数, 表示该变量或函数只能在本文件可见, 其他文件看不到, 也访问不到该变量或函数(interal linkage);
- C++语言的static关键子的四种用法:
- static关键字又有了两种新用法: 用于修饰class的数据成员, 即所谓的静态成员, 这种数据成员的生存期大于class的对象(实体/instance);
- 静态成员是每个class有一份, 普通数据成员是每个instance(实例)有一份, class variable(类变量)和instance variable(实例变量);
- 用于修饰class的成员函数, 即所谓的静态成员函数, 静态成员函数只能访问class variable和其他静态程序函数, 不能访问instance variable或instance method;
- 协议设计是网络编程的核心:
- 消息格式: XML, JSON, Protobuf, 难的是消息内容;
- 网络编程的三个层次:
- 读过教程和文档, 做过练习 -- 读过《UNIX网络编程》《TCP/IP详解》并理解TCP/IP协议, 读过本系统的manpage;
- 熟悉本系统TCP/IP协议栈的脾气;
- 有可能出现TCP自连接(self-connection), 程序应该有所准备;
- Linux内核会有bug, 比如某种TCP拥塞控制算法曾经出现TCP window clamping(窗口错位)bug, 导致吞吐量暴跌, 可以选用其他拥塞控制算法来绕开(work around)这个问题;
- 自己写过一个简单的TCP/IP stack;
- TCP网络编程有三个例子最值得学习研究: 分别是echo, chat, proxy都是长连接协议;
- proxy的作用: 连接的管理更加复杂, 既要被动接受连接, 也要主动发起连接, 既要主动关闭连接, 也要被动关闭连接, 还要考虑两边速度不匹配;
- 三本必看的书:
- 谈到Unix编程和编程编程, W.Richard Stevens是个绕不开的人物;
- [APUE]、两卷《UNIX网络编程》、三卷《TCP/IP详解》;
- [UNPv2]其实跟网络编程关系不大, 是[APUE]在多线程和进程间通信(IPC)方面的补充;
- 《TCP/IP详解》三卷, 用处不同, 应该区别对待;
- 第一本《TCP/IP Illustrated, Vol. 1: The Protocols》(TCP/IP详解);
- 从使用者(程序员)的角度, 以tcpdump为工具, 对TCP协议抽丝剥茧, 娓娓道来;
- TCP作为一个可靠的传输层协议, 其核心有三点:
- Positive acknowledgement with retransmission(对重传的积极响应) -- 可靠性;
- Flow control using sliding window(包括Nagle算法等) -- 提高吞吐量;
- Congestion(拥塞) control(包括slow start、congestion avoidance、fast retransmit) -- 防止过载造成丢包;
- TCP像是一个自适应的节流阀, 根据管道的拥堵情况自动调整阀门的流量;
- 第二本《Unix Network Programming, Vol.1:Networking API》统称UNP;
- UNP是Sockets API的权威指南;
- 网络编程远不是使用那十几个Sockets API那么简单, 一定要熟悉TCP/IP协议及其外在表现(比如打开和关闭Nagle算法对收发包延时的影响);
- UNP中问版《UNIX网络编程》翻译得相当好, 译者杨继张先生是真懂网络编程的;
- UNP很详细, 面面俱到, UDP、TCP、IPv4、IPv6都讲到了;
- 讲得太详细, 重点不够突出;
- 在具备基础之后, 学习任何新东西, 都要抓住主线, 突出重点, 对于关键理论的学习, 要集中精力, 速战速决;
- 作者是先看的TCPv1, 花了大约两个月的时间, 然后再读UNP和APUE;
- 第三本《Effective TCP/IP Programming》;
- 还值得一看的书:
- 《TCP/IP Illustrated, Vol.2: The Implementation》, 称为TCPv2;
- 工作中大可以把IP视为host-to-host的协议;
- 《Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects》, 简称POSA2;
- 这本书总结了开发并发网络服务程序的模式, 是对UNP很好的补充;
- POSA2强调模块化, 网络通信交给library/framework去做, 程序员写代码只关注业务逻辑(这是非常重要的思想);
- 这本书对深入理解常用的event-driven网络库(libevent, Java Netty, Java Mina, Perl POE, Python Twisted)也很有帮助;
- POSA2的代码是示意性的, 思想很好, 细节不佳;C++代码没有充分考虑资源的自动化管理(RAII);
- 很多企业内部使用C++来构建自己的分布式系统基础架构, 并且有替换Java开源实现的趋势;
- 学习C++只需要读一本大部头《The C++ Programming Language》或《C++ Primer》;
- 《C++ Primer》的主要内容是精解C++语法(syntax)与语意(semantics)并介绍C++标准库的大部分内容(含STL);
- C++的开源库: Google的Protobuf, leveldb, PCRE的C++封装, 还有就是作者的muduo库;
- 如有时间可以读读Chromium中基础库源码, 在读Google开源的C++代码时要连注释一起细读;
- 不建议一开始就读STL或Boost的源码, 因为编写通用的C++模板库和编写C++应用程序的知识体系相差很大;
- 《Effective C++中文版》,《泛型编程与STL》, 《C++编程规范》;
- 避免写出依赖于函数实参求值顺序的代码, C++操作读的优先级、结合性与表达式的求值顺序是无关的;
- Google的C++编程规范和LLVM编程规范;