当前位置: 首页 > 编程日记 > 正文

代码之美——Doom3源代码赏析2

http://www.csdn.net/article/2013-01-17/2813778-the-beauty-of-doom3-source-code/2

摘要:Dyad作者、资深C++工程师Shawn McGrathz在空闲时翻看了Doom3的源代码,发出了这样的惊叹:“这是我见过的最整洁、最优美的代码!”“Doom 3的源代码让我对那些优秀的程序员刮目相看。”因此有了本文。

最少模板


id“犯了不少C++的禁忌”,他们重写了所有需要的STD函数。我个人对STD爱恨交织。在Dyad,我调试构建时常使用它来管理动态资源;在发布时又会处理所有的资源,避免使用任何STL函数,以求尽快地加载。STL很不错,因为它提供了快速的通用数据结构;它又很糟糕,因为使用它经常导致代码丑陋不堪,甚至容易出错。例如std::vector<T>类,如果我想迭代每一个元素:

在C++11中要简单些:

但我个人并不喜欢自动化,虽然它简化了代码编写,却导致代码更难阅读,最起码我现在是这么认为的。

STD有的函数、算法甚至非常荒谬,比如要从std::vector中删除一个值:

你必须每次都能拼写正确!id除去了其中所以含糊不清的部分:他们使用自己的通用容器、字符串类等等。他们编写的类比起STL要更加专一,易于理解。id还尽可能地避免使用模板,而且使用自己定制的内存分配器。STD代码里则充斥着无意义的垃圾模板,而且不易于阅读。

C++代码很难写好,所以你需要不断地努力,不相信的话可以去看看Microsoft和GCC的STD代码,这是我见过的最难看的代码!

id通过不滥用泛型就简单地解决了这个问题。他们编写了HashTable<V>和HashIndex类,HashTable强制key类型是const char *,而HashIndex是int->int对。这看起来像是很糟糕的C++实例。他们“本应该”只有一个HashTable类,然后为编写局部特殊化:KeyType = const char *,然后专门 <int, int>。

当然,id的做法完全正确,也保证了代码的优美。

对比更鲜明的是,Hash生成“C++优秀实践”和id做法的比较:

为特定类型专门化:

这样你可以把ComputeHashForType当作HashComputer传给HashTable:

这和我的做法很相近,看起来很聪明,但实际上很难看!因为,如果可选的模板参数很多怎么办?

这种情况下函数定义要更糟:

如果没有代码高亮,我甚至不能区分出方法名!

我也曾看到其它引擎试图通过卸载模板参数规范到无数的typedef,这更糟糕!也许这利于理解,但却导致了本地代码和整个系统逻辑的断层,所以缺乏美感。例如:

以及:

你这样使用两者:

你会产生疑惑:StringHashTable内存分配器——StringAllocator会涉及全局内存吗?这里导致了混淆,于是你又需要返回之前的代码检查(循环)……

Doom的做法和常规C++逻辑完全相反:它尽可能地避免泛型,除非有特别的意义。Doom的HashTable需要生成hash值时怎么办?它只需要调用idStr::GetHash()。

C语言的余韵


虽然我不清楚id团队其他人的出身如何,但John Carmack基本上可以说是开发C应用起家的,id在Quake III之前开发游戏用的都是C语言。我见过很多没有C开发功底的C++程序员,编写代码都有非常重的C++特色,上面过度使用模板的情况只是其中一例,其它还有:

  • 过度使用set/get方法
  • 使用字符串流
  • 过度使用操作符重载

id在以上方面都做得非常完美。

通常很多人会这样创建一个类:

这样不仅浪费行数,还需要花费更多的时间编来写和阅读代码。相比之下:

如果你经常为var自增某个数字n呢?

相比于:

上面的例子明显容易阅读和编写。

id从不使用字符流,字符流通常包含糟糕的操作符重载:<<

例如:

虽然它有很多好处,但是很难看,而且语法也让人讨厌。

id选择printf()来代替,这样也易于阅读理解。我同意这样的决定。

另一方面,Doom还尽量避免操作符重载。虽然操作符重载是非常优秀C++特性,但没有操作符重载也就没有歧义,更便于编写和阅读。

横向空间


这是我从Doom的代码中最大的收获,原来我是这样编写代码的:

根据Doom3的编码标准,始终使用相对于4个空格的tab,水平对齐其中所有类的定义:

他们很少在类的定义中嵌入内联函数,我看到的唯一一次是代码和函数声明写在了同一行,这种做法有点不符合规范。这种类定义的组织方式非常容易解析,不过需要更多的时间来编写。

我讨厌多余的代码编写,但这种情况下,我只需要这次稍微多做一点工作,其他程序员在之后接手时就可以省下很多功夫。相信这里的Doom3编程规范能够帮助你理解其代码之美。(有网友称Google的C++编程规范与其也有很多相似之处。)

方法名


我认为Doom在方法名方面缺乏规范,我个人会尽可能地以动词开头命名方法:

比这样要好得多:

以下是John Carmack本人的回复:


从某些角度来看,我认为Quake3的代码更加整洁,算是我C语言代码的风格的一次进化,而非C++风格的第一次迭代。当然也可能因为总代码行数更少,或者是因为我已经10年没看过它的代码引起的错觉。我认为“好的C++”在可读性方面比“好的C语言”更好,其它方面大体相同。

我开始掌握C++是在Doom3开发的时候——在这之前,我有丰富的C语言编程经验,因为NeXT Objective-C编程的原因也有OOP(面向对象编程)背景,因此在使用C++的时候并没有对其使用和习惯进行适当针对性的研究。现在回想起来,真希望提前看过Effective C++这样的教程。团队里其他程序员虽然之前有C++编程经验,但基本上也是按照我选择和设置的风格在编程。

很多年来,我一直怀疑模板,一直在克制地使用它,不过最终确定自己更喜欢强类型,而非充满奇怪的代码的头文件。关于STL的争论在id内部一直没有停息,显得很有生气。回想Doom3开始开发的时候,使用STL基本上算不得好主意,直到现在,即使是在游戏中我们也仍然在争论这件事。

关于const,我直到现在基本上还是一个nazi,我会斥责每一个不尽可能常量化变量和参数的程序员。

我现在的风格主要是在向函数式编程靠近,这样可以舍去很多旧习,逐渐远离一些OOP的方向。

关于C++函数式编程John Carmack写过一篇《Functional Programming in C++》值得一读!《程序员》对这篇文章做过编译。

附:id-Software源代码网站(GitHub)|Doom3源代码(GitHub)|Doom3 BFG源代码(GitHub)

转载于:https://www.cnblogs.com/code-style/p/3558308.html

相关文章:

什么是JavaBean

按着Sun公司的定义&#xff0c;JavaBean是一个可重复使用的软件组件。实际上JavaBean是一种Java类&#xff0c;通过封装属性和方法成为具有某种功能或者处理某个业务的对象&#xff0c;简称bean。由于javabean是基于java语言的&#xff0c;因此javabean不依赖平台&#xff0c;具…

关于 linux io_uring 性能测试 及其 实现原理的一些探索

文章目录先看看性能AIO 的基本实现io_ring 使用io_uring 基本接口liburing 的使用io_uring 非poll 模式下 的实现io_uring poll模式下的实现io_uring 在 rocksdb 中的应用总结参考先看看性能 io_uring 需要内核版本在5.1 及以上才支持&#xff0c;liburing的编译安装 很简单&am…

添加引用方式抛出和捕获干净的WebService异常

转载&#xff1a;http://www.cnblogs.com/ahdung/p/3953431.html 说明&#xff1a;【干净】指的是客户端在捕获WebService&#xff08;下称WS&#xff09;抛出的异常时&#xff0c;得到的ex.Message就是WS方法中抛出的异常消息&#xff0c;不含任何“杂质”。 前提&#xff1a;…

Java项目:车租赁管理系统(java+Gui+文档)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 功能介绍&#xff1a; 登陆界面、管理员界面、用户界面、汽车租赁文档 系统主页&#xff1a; SuppressWarnings("serial") public class SystemMainView extends JFrame implements ActionListe…

TFS中的测试计划(十)

现在有一个测试用例&#xff0c;用来测试登录&#xff0c;并且有两组测试数据。打开团队项目的web门户的测试。新建一个测试计划。命名为测试计划1添加完测试计划后&#xff0c;就可以向这个计划里添加测试用例了&#xff0c;选择登录测试。运行测试&#xff0c;就会生成下图左…

跟着Rocskdb 学 存储引擎:读写链路的代码极致优化

文章目录1. 读链路1.1 FileIndexer1.1.1 LevelDB sst查找实现1.1.2 Rocksdb FileIndexer实现1.2 PinnableSlice 减少内存拷贝1.3 Cache1.3.1 LRU Cache1.3.2 Clock Cache1.4 ThreadLocalPtr 线程私有存储1.4.1 version系统1.4.2 C thread_local vs ThreadLocalPtr1.4.3 ThreadL…

Java项目:人力管理系统(java+Gui+文档)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 功能介绍&#xff1a; 角色员工、管理员&#xff0c;员工信息表&#xff0c;查询、更新&#xff0c;修改&#xff0c;移除、添加 用户管理控制层&#xff1a; /*** author yy*/Controller RequestMapping(…

senfile函数实例的运行过程截图

//要传输的文件内容如下所示&#xff1a; 启动服务器&#xff0c;等待客户端连接&#xff08;在同一台主机上模拟的&#xff09; 客户端远程登录&#xff0c;这里是在本地登录 这个要注意一点就是远程登陆的时候一定要带上端口号不然连接失败&#xff01;&#xff01;转载于:ht…

马年计划2014-2-21

新的一年到来了&#xff01; 刚刚过去的一年里&#xff0c;我已浪费很多时光&#xff01; 新年新气象&#xff0c;为避免重蹈覆辙&#xff0c;此时我必须要立个新年计划&#xff0c;马年计划&#xff01; &#xff08;1&#xff09;一天必须做两道ACM题。 &#xff08;2&#…

java jsp页面如何添加C标签

在https://mvnrepository.com/找两个jar包分别是&#xff1a; <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> <version>1.2</version> </dependency> <dependency> <g…

如何用 ndctl/ipmctl 管理工具 配置不同访问模式的pmem设备

文章目录1 PMEM 底层架构2 PMEM 逻辑架构3 ipmctl 创建 不同模式的 region3.1 安装3.2 创建AppDirect mode的region3.3 创建 Memory Mode模式3.4 创建 混合模式3.5 查看创建的结果4 ndctl 创建不同类型的 namespaces4.1 安装4.2 创建/删除 一个任意类型的namespace4.3 指定类型…

[PHP]php基础练习题学习随笔

1、解释一下PHP中常量、变量、可变变量并举例说明&#xff1b;超级全局变量有哪些&#xff1f; 常量是单个值的标识符&#xff08;名称&#xff09;&#xff0c;通过define()设置&#xff0c;在脚本中无法改变该值&#xff0c;常量自动全局。<?php #对大小写不敏感为true&a…

Java项目:进销存系统(java+Gui)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 功能介绍&#xff1a; 基本信息管理、库存管理、销售管理、订单管理、日志管理、供应商基本信息、员工基本信息、商品信息、入库管理、出库管理、剩余库存 商品信息控制层&#xff1a; /*** <p>* 前…

IDP申请直到软件上架流程 - iOS

第一&#xff1a;IDP的申请 1.先在iPhone DevCenter上注册成为iphone developer 2.加入iPhone开发程序项目iPhone Developer Program Apply Now 3.打算收费的都建议选择99刀那个&#xff0c;QTY是个数的意思。1就好。 4.选择地区china&#xff0c;(很早之前没有china&#xff0…

灭霸—个人冲刺(4)

灵魂三问&#xff1a;昨天做了什么&#xff1f;1.手机验证码 2h 2.整体框架搭建尝试 2h 目标任务量&#xff1a;100% 完成任务量&#xff1a;100% 今天要做什么&#xff1f;1.数据库建立及连接 16h 遇到困难没有&#xff1f;2.整体框架搭建时因为连接服务器分为三类&#xf…

关于 Rocksdb 的 EnvWrapper 作用的小讨论

临下班前一位做引擎的小伙伴提了个小问题&#xff0c; Rocksdb 实现了非常多的Env backend 这一些backend 可以让用户根据自己需求创建不同 公共接口backend&#xff0c;来实现自己的文件操作或者公共线程池操作。 Env* env new rocksdb::HdfsEnv(FLAGS_hdfs) 问题是&#xf…

corepython第九章:文件和输入输出

学习笔记: OS模块代码示例: 1 import os2 for tmpdir in (/tmp,rc:\users\administrator\desktop):3 #如果存在括号里面的目录&#xff0c;则break4 if os.path.isdir(tmpdir):5 break6 #如果不存在&#xff0c;则tmpdir为空值&#xff0c;即False7 else:8 pri…

Java项目:学生管理系统(无库版)(java+打印控制台)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 功能介绍&#xff1a; 学生成绩管理系统成绩表 用户管理操作&#xff1a; /*** 用户管理操作*/ Controller RequestMapping("/user") public class UserController {Autowiredprivate UserServi…

构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(12)-系统日志和异常的处理②...

原文:构建ASP.NET MVC4EF5EasyUIUnity2.x注入的后台管理系统&#xff08;12&#xff09;-系统日志和异常的处理② 上一讲我们做了日志与异常的结果显示列表&#xff0c;这一节我们讲要把他应用系统中来。 首先我们在App.Common类库中创建一个通用类ResultHelper&#xff0c;这个…

爬取猫眼怦然心动电影评论

作业要求来源&#xff1a;https://edu.cnblogs.com/campus/gzcc/GZCC-16SE1/homework/3159 可以用pandas读出之前保存的数据&#xff1a; newsdf pd.read_csv(rF:\duym\gzccnews.csv) 一.把爬取的内容保存到数据库sqlite3 import sqlite3with sqlite3.connect(gzccnewsdb.sqli…

TCMalloc(Thread-Caching malloc) 基本设计原理

文章目录背景如何使用架构概览1. TCMalloc Front-end1.1 小对象和大对象的内存分配过程1.2 内存释放过程1.3 Per-CPU mode1.4 Per-thread mode1.5 per-cpu 和 per-thread 运行时内存管理算法对比2. TCMalloc Middle-end2.1 Transfer Cache2.2 Central Free List2.3 Pagemap 和 …

Java项目:控制台商城系统(java+打印控制台)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 功能简介&#xff1a; 客户信息管理、商品信息管理、购物信息管理、退出系统 显示系统主菜单&#xff1a; public class SystemMenu {//显示系统主菜单public void showMainMenu(){System.out.println(&qu…

PAT (Basic Level) Practise (中文)-1025. 反转链表 (25)

PAT (Basic Level) Practise &#xff08;中文&#xff09;-1025. 反转链表 (25) http://www.patest.cn/contests/pat-b-practise/1025 给定一个常数K以及一个单链表L&#xff0c;请编写程序将L中每K个结点反转。例如&#xff1a;给定L为1→2→3→4→5→6&#xff0c;K为3&am…

初识Quartz(三)

为什么80%的码农都做不了架构师&#xff1f;>>> 简单作业&#xff1a; package quartz_project.example3;import java.util.Date;import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Job…

内存分配器设计的演进

文章目录栈内存空间是否够用系统调用申请内存最简单的内存分配器实现 -- bump allocator可扩容的 Bump alloactor通过free-list 管理的 allocator通过size-buckets 维护多个free-list 的 allocatorCache friendly allocator需要考虑更多问题的allocator性能易用性本文希望描述一…

Android OpenGL ES(十一)绘制一个20面体 .

前面介绍了OpenGL ES所有能够绘制的基本图形&#xff0c;点&#xff0c;线段和三角形。其它所有复杂的2D或3D图形都是由这些基本图形构成。 本例介绍如何使用三角形构造一个正20面体。一个正20面体&#xff0c;有12个顶点&#xff0c;20个面&#xff0c;30条边构成&#xff1a;…

Java项目:学生选课系统(java+javaweb+jdbc)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 功能介绍&#xff1a; 用户菜单、学生管理、教师管理、课程管理、成绩排名查询 学生管理控制层&#xff1a; Controller RequestMapping("/student") public class StudentController {private …

Xtrabackup对mysql全备以及增量备份实施

Xtrabackup对mysql全备以及增量备份实施1.完全备份与恢复本文使用的是centos5.8 64位系统&#xff0c;mysql 使用5.5.35.如果要使用一个最小权限的用户进行备份&#xff0c;可基于以下&#xff1a;mysql> createuser bkuserlocalhost identified by redhat;mysql> grant …

js浅拷贝和深拷贝

浅度拷贝&#xff1a;复制一层对象的属性&#xff0c;并不包括对象里面的为引用类型的数据&#xff0c;当改变拷贝的对象里面的引用类型时&#xff0c;源对象也会改变。 深度拷贝&#xff1a;重新开辟一个内存空间&#xff0c;需要递归拷贝对象里的引用&#xff0c;直到子属性都…

关于 fallocate 文件系统预分配 的一些细粒度测试

文章目录Rocksdb 中的预分配Fallocate in rocksdb 性能测试Fallocate 使用 以及 对应配置的行为API 使用不同 Mode 的行为分配磁盘空间释放磁盘空间折叠/裁剪 文件内容清零文件 扩容文件Rocksdb 中的预分配 预分配文件存储空间 在存储引擎中用的还是比较频繁的&#xff0c;尤…