反思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编程规范;

Mudo C++网络库第十一章学习笔记的更多相关文章

  1. Mudo C++网络库第六章学习笔记

    muduo网络库简介 高级语言(Java, Python等)的Sockects库并没有对Sockects API提供更高层的封装, 直接用它编写程序很容易掉到陷阱中: 网络库的价值还在于能方便地处理并 ...

  2. Mudo C++网络库第四章学习笔记

    C++多线程系统编程精要 学习多线程编程面临的最大思维方式的转变有两点: 当前线程可能被切换出去, 或者说被抢占(preempt)了; 多线程程序中事件的发生顺序不再有全局统一的先后关系; 当线程被切 ...

  3. Mudo C++网络库第三章学习笔记

    多线程服务器的适用场合与常用编程模型 进程间通信与线程同步; 以最简单规范的方式开发功能正确.线程安全的多线程程序; 多线程服务器是指运行在linux操作系统上的独占式网络应用程序; 不考虑分布式存储 ...

  4. Mudo C++网络库第七章学习笔记

    muduo编程示例 muduo库是设计来开发内网的网络程序, 它没有做任何安全方面的加强措施, 如果在公网上可能会受到攻击; muduo库把主动关闭连接这件事分成两步来做: 如果主动关闭连接, 会先关 ...

  5. Mudo C++网络库第五章学习笔记

    高效的多线程日志 日志(logging)有两个意思: 诊断日志(diagnostic log), 常用日志库提供日志功能; 交易日志(transaction log), 用于记录状态变更, 通过回放日 ...

  6. 网络库Alamofire使用方法学习笔记

    Github地址 由于Alamofire是swift网络库,所以,以下的所有介绍均基于swift项目 导入Alamofire 以下为使用cocoapods导入,其余的方式请参考官网 source 'h ...

  7. 《Linux内核设计与实现》 第一二章学习笔记

    <Linux内核设计与实现> 第一二章学习笔记 第一章 Linux内核简介 1.1 Unix的历史 Unix的特点 Unix很简洁,所提供的系统调用都有很明确的设计目的. Unix中一切皆 ...

  8. 《Linux内核设计与实现》第一、二章学习笔记

    <Linux内核设计与实现>第一.二章学习笔记 姓名:王玮怡  学号:20135116 第一章 Linux内核简介 一.关于Unix ——一个支持抢占式多任务.多线程.虚拟内存.换页.动态 ...

  9. 《Linux内核设计与实现》课本第五章学习笔记——20135203齐岳

    <Linux内核设计与实现>课本第五章学习笔记 By20135203齐岳 与内核通信 用户空间进程和硬件设备之间通过系统调用来交互,其主要作用有三个. 为用户空间提供了硬件的抽象接口. 保 ...

随机推荐

  1. Mac下显示网页全屏快捷键

    control+command+F mac下谷歌浏览器全屏时隐藏头部:(隐藏标签页和地址栏) command+shift+B

  2. windows cmd命令 批处理bat 导增量jar包【原】

    下载地址 https://pan.baidu.com/s/1cIyCbG 导jar包 @echo off setlocal enabledelayedexpansion echo ---------- ...

  3. PHPMYWIND4.6.6前台Refer头注入+后台另类getshell分析

    下载链接 https://share.weiyun.com/b060b59eaa564d729a9347a580b7e4f2 Refer头注入 全局过滤函数如下 function _RunMagicQ ...

  4. adduser Ubuntu添加sudo用户

    第一种方法: 添加sudo用户 当你安装Ubuntu的时候,它会自动添加第一个用户到sudo组,允许这个用户通过键入其自身帐户密码来获得超级用户(root)身份.然而,系统不会再自动添加其他的用户到s ...

  5. jquery 控制 video 视频播放和暂停

    $('video').trigger('play'); $('video').trigger('pause'); 参考:https://blog.csdn.net/arvin0/article/det ...

  6. ubuntu终端命令启动matlab方法

    让所有用户都有权限使用matlab,在终端输入 sudo gedit /etc/profile 在后行写 export MATLABPATH=/home/ubuntu/MATLAB/R2016b:$M ...

  7. dbms_redefinition在线重定义表结构

    dbms_redefinition在线重定义表结构 (2013-08-29 22:52:58) 转载▼ 标签: dbms_redefinition 非分区表转换成分区表 王显伟 在线重定义表结构 在线 ...

  8. Docker 容器启动 查看容器状态 - 四

    1.容器两种方式进行启动 一种是基于创建一个容器并启动 docker create docker start 另一种 使用 run 创建自动启动:是状态下的停止 启动 docker start ngi ...

  9. [C++]Linux之头文件sys/types.h[/usr/include/sys]

    1.查找<sys/types.h>文件 一般地,Linux的C头文件<sys/types.h>路径在如题的途径:/usr/include/sys下,然而博主[Linux For ...

  10. 第26月第20天 springboot

    --------------------- 1.pom.xml中添加支持web的模块: <dependency> <groupId>org.springframework.bo ...