Mudo C++网络库第十一章学习笔记

反思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++中, 提高抽象的层次并不会降低效率;
  • 模板与泛型;
  • 数据抽象是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, 那么要按规矩来;
      • 检查代码中的内存错误;
      • 优化性能;
      • 获得内存使用的统计数据;
  • 脚本语言解释器代码:
    • Python的代码很好读;
  • 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编程规范;
上一篇:CSS布局:让页底内容永远固定在底部


下一篇:JavaWeb学习笔记——Tomcat相关