聆听大师的声音(EFC)

1.使用const和inline而不是#define
define可以做成全局的,static const做成全局的则有欠面向对象。
define需要一致的使用方式,否则和散落在各处的static const一样难以修改(和魔术数字遇到同样的问题)
建议define单放在一个文件中并且使用严格编码规范保证按照设计思路使用。
const面向对象的做法是封装成class中的static成员变量,但这样需要额外定义。
封装成class的const使用起来需要较长引用名,但是可以不同用途数据使用不同的类,方便管理、修改。
总之,魔术数字隐患很大,但是要避免魔术数字,并不是define或const可以解决的,需要严格的编码规范和执行力度。
2.尽量使用<iostream>而不是<stdio.h>
printf和scanf我基本不用了,可是stdio思想的东西,我还是一直在用,譬如sprintf_s,而且已经碰到了问题,sprintf_s参数不对的时候编译器不会给出任何提示,当程序crash掉的时候,你发现,原来sprintf_s的参数少了一个。所以,我一直一来试图使用stream(包括strstream)替换stdio的东西,但是碰到了其他问题。
iostream还是会在类型上出问题,譬如你想把一个char类型的数据输出的时候,iostream可能输出了一个无法显示的字符,在这个时候,强制转换成int再输出可以得到正确的显示结果。
<string>和<string.h>是完全不同的两个东西可以帮助理解h的区别。
3.尽量使用new和delete而不用malloc和free
这之中最大的差别在于构造、析构函数,还有一个就是使用的时候不要交叉。
另外注意在使用place new的时候,这两者的区别可以被利用。
4.尽量使用C++风格的注释
其实我已经拒绝使用c风格的注释了,只不过没有意识到把这个东西做成规范。在当今c++编译器支持下,回避任何使用c风格的注释是个不错的主意。
5.对应的new和delete要采用相同的形式
这点知道delete和delete[]区别的人都知道,但实际使用时要不出问题,可就没这么容易了。
因为typedef可以隐藏实际类型。
6.析构函数里对指针成员调用delete
对于指针,初始化为NULL就可以放心使用delete而不用关心是否用new操作过。
7.预先准备好内存不够的情况
其实new和delete就是调用目标类的operator new和operator delete,明白这一点对理解修改new行为很有帮助。
8.写opertor new和operator delete时要遵循常规
当new失败的时候,它会交给出错处理函数,并期望出错处理函数可以解决这个问题,然后继续调用new方法。也就是说,new如果经由出错处理函数操作,那再操作返回的时候new会再被调用一次。自己写operator new的时候,也应该遵循这个规则。
9.避免隐藏标准形式的new
在类内部定义了operator new,则隐藏了对系统new的调用。
10.如果写了operator new就要同时写operator delete
这主要是在使用小内存分配策略的时候出的问题,你可以在new中使用小内存分配策略获取内存,但是如果不重写delete,则默认delete操作了特定策略的小内存块,会引发内存问题。

11.为需要动态分配内存的类声明一个拷贝构造函数和一个赋值构造函数
构造函数、析构函数、赋值操作符。
如果operator=没有被定义,则当需要的时候,编译器会进行memcpy,当对象有指针成员的时候,memcpy会在拷贝了指针,导致内存泄漏以及有可能的内存重复被删除。
12.尽量使用初始化而不要在构造函数里赋值
使用初始化列表的好处是 可以使引用类型可以赋值、可以避免构造函数的开销。
如果有太多的成员变量,那只能使用初始化函数了,常见的init函数就是这个用意。
13.初始化列表中成员列出的顺序和它们在类中的声明的顺序相同
这点在深入C++对象模型中已经详细说明,编译器保证按声明顺序初始化。
14.确定基类有析构函数
无故声明虚析构函数和永远不去声明一样是错误的。
在这里,纯虚析构函数可以“提醒”编译器使用最后定义的析构函数。
15.让operator=返回*this的引用
赋值运算符是由右向左结合的。
当赋值运算被括号操作改变顺序的时候,operator=如果不是返回的*this,则可能“意外”改变操作串中的某个不想改变的值。
16.在operator=中对所有数据成员赋值
operator=不能完全赋值的情况多发生在两处 后期添加成员但忘了修改operator=操作符、继承类的operator和赋值构造函数中。
operator=中可以使用baseclass::operator=显式调用。
拷贝构造函数可以使用初始化列表。
17.在operator=中检查给自己赋值的情况
想办法处理对自己的赋值,不同情况有不同的检测方式。
18.争取使类的接口完整并且最小
类接口太多难以维护使用的问题我一直在寻找解决方法,现在看来,如果类的数量膨胀不厉害的话,控制每个类的接口在10个左右是个不错的选择。这里要涉及合理拆分类的问题,最核心的是合理设计类,拆分数据也就拆分了类。
试想一下,对于只有一个数据的类,接口不可能多到哪里。所以接口太多的原因是内部数据太多关联太大。
19.分清成员函数、非成员函数和友元函数
20.避免public接口出现数据成员
不使数据成员成为公有的的好处是免去你使用括号还是不使用括号的烦恼。丷丷
const可以更精确的控制只读操作。
即使目的是直接获取成员数据,也应该使用函数封装,因为你极有可能需要优化,如果直接访问数据成员,优化后的使用方法可能就不只是访问成员数据了。也就是说,直接使用数据成员不利维护、没有太多优化余地,是非可持续发展。
21.尽量使用const
const函数真的要修改成员数据的话,可以使用mutable
22.尽量使用“传引用”而不是“传值”
C++对象模型说 如果需要,编译器会帮你合成一个operator=,如果成员对象没有定义operator=,则编译器进行memcpy,如果成员对象有定义operator=,则编译器会调用成员定义的赋值行为。
传引用的一个隐患是数据意外被修改,使用const可以避免这个问题。从这里开始喜欢上引用和const吧。
23.必须返回一个对象时不要试图返回一个引用
回传一个对象的时候,最大的开销是构造函数,而引用可以避免构造函数开销,但这二者的结合却是一个可怕的想法。
24.在函数重载和设定参数缺省值间慎重选择
确实有可用默认参数并且可以找到一致算法的时候使用缺省参数。
尤其当当各个算法不一致的时候,使用重载是更好的选择。
25.避免对指针和数字类型重载
NULL和0可是对这种重载的一个巨大挑战。
26.当心潜在的二义性
二义性可能由于隐式转换产生、也可能有多重继承产生。
27.如果不想使用隐式生产的函数就要显式地禁止它
赋值、拷贝、析构三个是息息相关的,如果有内存行为的话,少一个相当危险。
28.划分全局名称空间
当项目再一步做大的时候,namespace就相当重要了,所以应该习惯这种手法。
29.避免返回内部数据的句柄
30.避免这样的成员函数:其返回值是指向成员的非const指针或引用,但成员的访问级别比这个函数要低
其实29、30是一致的,用特定的标签想阻止特定级别的访问,但是返回数据违反了这个级别的话,特定标签就没有意义了。如果非要做这种操作又不违反标签意义的话,可以使用const引用。
31.千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用
在我的编译器上(vs2005-vc8),如果有明显的返回局部变量(指针、对象),编译器会给出“warning C4172: 返回局部变量或临时变量的地址”的提示。
返回指针的引用(不是传指针的引用)可能源于效率心魔想让一切都使用引用优化,殊不知,引用和指针的效率是一样的。返回局部对象的引用合理,可以避免对象构造、拷贝,但这却陷入了另一个陷阱。其实,如果非要通过函数获得一个对象的引用,建议使用传入引用参数而不要用返回引用。
32.尽可能地推迟变量的定义
当你的代码风格不够优雅的时候,你的函数可能半途返回,当你的代码风格足够优雅的时候,你的函数可能因为异常等原因改变预订的执行顺序,推迟变量的定义可以在非常规流程执行代码时节省有可能的操作。
另初始化构造函数比默认构造函数加operator=操作效率显然高很多。
33.明智地使用内联
和模板一样,比模板更苛刻,inline函数必须放在头文件中。否则编译器(我的编译器vc8)抱怨说找不到实现。
只在确实需要内联的地方才使用内联函数。
34.将文件间的编译依赖性降至最低
指针手段可以降低编译依赖性,引用手段也是一样。
尽可能使用类的声明而不要使用类的实现。
除非无法编译,不要在头文件中包含其他头文件,要不编译依赖太大。(当然,不在头文件中包含其他文件需要在实现文件中仔细调整头文件包含顺序。)
句柄类、信封类都是Bridge模式的不同名字。
工厂函数、虚构造函数也都是指的同一东西。
35.使公有继承体现“是一个”的含义
private则表示了has-a的关系。public表示了is-a的关系,这里的着力点是保证公有继承有is-a的关系,也就是说基类中的任何方法都是子类合理的,基类中的某个方法对子类不合理,那要么继承有问题,要么基类设计有问题。
36.区分接口继承和实现继承
只继承接口可以使用纯虚函数
接口、实现继承但允许继承者修改可以使用虚函数
不允许修改的接口、实现继承可以使用非虚函数。
37.决不要重新定义继承而来的非虚函数
在一个类中声明一个非虚函数实际上为这个类建立了一种特殊性上的不变性。继承类有is-a的关系,从这两种层次上来说,都不要这么做。
38.决不要重新定义继承而来的缺省参数值
在我的编译器上,这没有问题,但是从效率上分析,这有很大问题(在我的编译器上,即使“刻意”模拟缺省值不对的情况,也会被编译器发现)。
39.避免“向下转换”继承层次
你可以使用dynamic检测,虽效率不高,但至少不至出错。你也可以在基类中做纯虚函数。
40.通过分层来体现“有一个”或“用...来实现”
使某个类对象成为另一个类的数据成员,从而实现将一个类构筑在另一个类之上,这一过程为“分层”Layering。
从我个人的感觉来说,有一个的代码层次关系也可以用私有继承来实现,但是可以使用this引用,而包含则需要对应对象引用。
41.区分继承和模板
如果类型参数对行为操作没有影响,那使用模板是个不错的选择,如果类型和操作有耦合,使用继承更佳。
42.明智地使用私有继承
私有继承是一种纯粹的实现技术。
同样是用...来实现,除非必须不要使用私有继承而应该使用分层。(分层应该是最优先被使用的技术)
用作分层结构的类如果不希望被意外操作,可以使用虚继承实现这种代码目的而有保护了该类。
模板类型安全有问题但富有通用性,类强于类型因而某些时候通用性有限制。
43.明智地使用多重继承
在我的编译器上
如果两个基类有同样的访问权限、同意的名字,则编译器抱怨说函数访问不明确。可以使用static_cast处理(如果你不介意效率,dynamic当然没问题)
而强制定义不同访问权限的方法在我的编译器上行不通。
为了支持同名函数在Mi体系中的分别被继承,可以使用辅助类(方法)的方法实现。在我的编译器上,调用该方法的指针如果是派生类类型的话还是需要显示指定。
是啊,多重继承的确是潘朵拉的盒子。由于它的存在,设计者需要有超凡的远见。
可以用一个虚基类的方法把MI改造成SI。当项目大到一定层度的时候,最初的设计者要有这种预估能力。
44.说你想说的,理解你所说的
还记得我曾经在一个rpg游戏中把战斗类的战斗类设计为fight和pvpfight,于是有了一个tofight虚函数,在非常困难的情况下,我天真的以为fight的this指针(本质上的pvpfight指针)可以正确使用pvp战斗的方法。我的设计显示了我所想的,但是我却没有理解我所说的。
45.弄清C++在幕后为你所写、所调用的函数
如果你没有自己定义,编译器会为你生成拷贝构造函数、赋值运算符、析构函数、一对取址运算操作、一个缺省构造函数(如果你没有定义任何构造函数)
尤其注意这对取址操作符,在C++对象模型里也有提到,你尽量不要重载它,而且编译器厂商为了自己的目的或者标准要求很可能要处理这个操作。
我曾经很喜欢在使用了自定义operator=行为的类的层叠结构中再定义operator=确保调用了自己的operator=,但是C++对象模型告诉我们这没有意义,除了浪费时间。
还要注意的一点是operator=的时候,一定先处理一下是否对自己赋值(这是很大一个陷阱)。
46.宁可编译和链接时出错,也不要运行时出错
某些解释性语言提供了比较大的灵活性,但是却把很多时间花在了运行时检查上,C++强于类型的编译期检测,所以应该多使用C++类型验证上的长处开发程序。(类是类型的载体)
47.确保非局部变量对象在使用前被初始化
这里让你想到了单件模式的使用,其实,这条规则衍生过单件的一个实现风格。所以这里提到的把静态对象放在函数中以控制生命周期还是很有意义的。
48.重视编译器警告
要深层理解条建议。
但请不要自欺欺人的刻意掩饰警告,我曾有过这样的经历,在项目里非常严格要求警告问题,所以对有些类型不匹配,如果却不会发生问题,我会用static_cast处理掉警告,可是当其他人维护我的代码时,他竟然不管可能的越界问题直接static_cast掉警告,是的,你猜到了,编译的时候觉得世界如此美妙,出现问题的时候反复调试找不到问题抓狂。(而且我也却碰到过类型隐式转换出问题的情况,那是在brew平台上,所以考虑你的程序的移植性,不要过分依赖当前编译器的情况)
49.熟悉标准库
提到unicode我想到了点事情,vs2005 ship的mfc已经默认支持unicode了,如果你有旧的mfc使用经验,那在字符串转换上,你会碰到很多问题,所以可以尝试让自己尽可能的写支持unicode程序。
string替代了char*操作,提供了你想要的大多数操作,这个我也一直在提,避免使用c型字符串是个不错的选择。
另外c++异常是一个新手容易忽视的地方,值得一提。
50.提高对c++的认识
说点小事,解释一下C++对象模型里为什么老引用ARM文本+标准文本,这是因为The Annotated C++ Reference Manual在C++标准前是所谓的C++标准,而且国际标准就是基于ARM和已有的C标准制定的,而且国际标准新出的时候很多厂商还在坚持使用ARM规范。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值