Effective C++
文章平均质量分 76
lsfreeing
这个作者很懒,什么都没留下…
展开
-
条款36、绝不重新定义继承而来的缺省参数值
为简化问题,只能定义两种函数:虚函数和非虚函数。从前面我们知道,重定义继承而来的非虚函数永远是错误的。所以我们讨论“继承一个带有缺省参数值的虚函数”。 理由十分明确:1、虚函数系动态绑定(又称前期绑定) 2、缺省参数值是静态绑定(后期绑定)。对象的所谓静态类型,就是它在程序中被声明时所采用的类型。考虑以下:class Shape{ public:enum S翻译 2015-03-17 10:43:56 · 699 阅读 · 0 评论 -
条款39、明智审慎地使用private继承
TK32论证了public继承是is-a关系。类student继承自类Person。于是在编译必要时student暗自转变为Person。现在考虑private继承:class Person{….};class Student:private Person{….};void eat(const Person& p); //任何人都会吃void study(const Stude翻译 2015-03-19 19:22:47 · 752 阅读 · 0 评论 -
条款41、隐式接口和编译期多态
C++ template最初的发展动机:建立“类型安全type safe”的容器如vector,lis,map.后面随着使用的增多,我们发现模版有能力完成更多可能的变化。 容器虽好,但泛型编程(generic programming)更好:代码和其所处理的对象类型彼此独立。STL算法如for_each,find等就是这类编程的成果。 于是导出了模板元编程(tmplate metaprogr翻译 2015-03-24 19:17:42 · 470 阅读 · 0 评论 -
条款42、typename的双重意义
如下:templateclass Widget; //使用”class”templateclass Widget; //使用”typename”虽然使用了不同的关键字,但从C+的角度来说,声明模板参数时,关键字class 和typename意义完全相同。然而C+并不把class和typename视为等价。有时你一定得使用typename. 考虑下面一个函数模版,接受一个STL兼容翻译 2015-03-25 19:10:35 · 539 阅读 · 0 评论 -
第18条:要让接口易于正确使用,而不易被误用
第四章 设计与声明 软件设计是用来帮助规划软件功能的,通常以一般性构想开始,但最终都会被具体化,直至成为可用于开发的一系列具体接口。之后这些接口必须转换为 C++ 声明式。本章中,我们将解决“如何设计和声明优秀 C++ 接口”这一问题。我们以下面的建议开始(它可能是设计接口时要注意的最为重要的一点):接口应易于正确使用,不宜被误用。这引出了一系列更为具体的建议,其中我们将讨论十分宽泛的议题,包翻译 2014-12-15 14:29:14 · 496 阅读 · 0 评论 -
条款28:避免返回handles指向对象内部成分
假如定义一个矩形。为了让Rectangle对象尽可能小,我们不会把定义的这些点(左上角和右下角)置于对象内,而是用一个辅助struct再去指向它。如下代码:class point //点类{ public: point(int x,int y); ... void setX(int val); void setY(int val); ...}; str翻译 2015-01-18 09:48:36 · 359 阅读 · 0 评论 -
条款26 尽可能延后变量定义出现的时间
尽可能延后变量定义的出现,这样可以增加程序的清晰度并改善程序效率。 当定义一个变量(尽管从未使用),当程序控制流到达这个变量定义时,仍然会耗费构造和析构成本。考虑下述代码://过早定义变量s1 std::string fun1(const std::string& s1) { using namespace std;翻译 2015-01-15 10:50:42 · 465 阅读 · 0 评论 -
第17条:以独立语句将new创建的对象存储在智能指针中
假设有一个函数用来显示处理程序的优先级,另一个函数根据当前优先级为一个动态分配的 Widget 做一些处理:int priority();void processWidget(std::tr1::shared_ptr pw, int priority); 谨记“以对象管理资源”(参见第 13 条)。 processWidget 中可以使用智能指针来动态分配其需要处理的 Widget 。下面是翻译 2014-12-12 21:18:19 · 414 阅读 · 0 评论 -
第16条:成对使用的new和delete时要采取相同形式
请观察下面的代码有什么不妥之处:std::string *stringArray = new std::string[100];...delete stringArray; 似乎没问题, new语句与delete相匹配。然而这完全错误。这段程序将出现无法预知的行为。最起码的是,由于该 stringArray 所指向的 100 个 string 对象中的 99 个没有被析构函数所析构,很有可原创 2014-12-11 14:48:03 · 373 阅读 · 0 评论 -
条款29:为“异常安全”而努力是值得的
考虑一个背景图案GUI菜单类,运用于多线程环境。以mutex(互斥器)作为并发控制。如下代码:class Menu{ public: ... voidchangeBG(std::istream& imgSrc); //改变背景 ... priv翻译 2015-03-07 21:44:23 · 365 阅读 · 0 评论 -
条款43、学习处理模版化基类内的名称
考虑以下代码,功能为发送不同信息至不同部门,采用模版方法。class CompanyA{ public: void sendClearT(const std::string&msg); void sendEncrypt(cons std::string& msg);};class CompanyB{ public:翻译 2015-03-27 15:54:24 · 386 阅读 · 0 评论 -
条款30:透彻了解inlining的里里外外
inline函数比宏好用得多(TK2),编译器通常被设计用来浓缩那些“不含函数调用”的代码,所以编译器有力能对inline函数执行语境相关最优化。 但inline也有另一面,对函数调用都以函数本体替换之,会增加目标代码的大小(object code)。在内存有限的环境中,造成的代码膨胀会导致换页行为,导致效率损失。换言之,如果inline函数本体很小,编译器针对“函数本体”产生比“函数调用翻译 2015-03-08 15:05:25 · 402 阅读 · 0 评论 -
条款32、确定你的public继承塑模出is-a关系
bublic继承意味着“is-a”关系。即一个D类对象(Derived)同时也是一个B类对象(Base)。反之则不成立。B相比D更一般化,而D相对B更特殊化。考虑如下:class Person {…};class student: public Person{…}; 承上,C+中任何函数期望获得类型Person(指针或引用)的实参,也愿意接受Student对象(指或引)、void eat翻译 2015-03-10 20:32:06 · 484 阅读 · 0 评论 -
条款38、根据复合塑模has-a或“根据某物实现出”
复合(compsition)是这样一种关系:某种类型对象内含其他类型对象。如:class Address{….};class PhoneNun{…};class Person{ private: //合成物成分 std::stringname; PhoneNum phone; Address addr;};上面Person对象由其他三部翻译 2015-03-18 16:43:55 · 547 阅读 · 0 评论 -
条款36、绝不重新定义继承而来的非虚函数
考虑以下代码,D公有派生自B,B有一个成员函数mf.class B{public:void mf();…};class D: public B{…};D x; //D类对象B* pb=&x; //行为1pb->mf();D* pd=&x; //行为2pd->f(); 很明显行为1、2调用会相同,都是基类的mf,理应如此。但如果mf是非虚函数,而且D有定义自己的m翻译 2015-03-14 15:39:13 · 659 阅读 · 0 评论 -
条款35、考虑虚函数以外的选择
假设一个人物类,它有一个表现健康程度的的成员函数heath,返回一个数值表示人物健康程度。不同人物可能有不同的计算健康程度的方法,将其声明为虚函数是再明白不过的方法:class Character{public: virtual int heath () const; //返回健康指数,派生类可重载…} health方法未声明为纯虚,暗示有计算健康指数的缺省方法。这个翻译 2015-03-14 10:07:44 · 406 阅读 · 0 评论 -
条款34、区分接口继承和实现继承
public继承由两部分组成:函数接口继承和函数实现继承。 设计类时,我们有不同的需求:1、Derived只继承成员函数的接口(也就是声明) 2、Derived同时继承接口和实现,但又能覆写继承的实现(override) 3、Derived同时继承接口和实现,但不允许覆写任何东西。为表现上面的差异,考虑以下代码:class Shape{public: virtu翻译 2015-03-13 14:56:39 · 478 阅读 · 0 评论 -
条款33、避免遮掩继承而来的名称
我们都知道,内层作用域会掩盖外层作用域的名称。至于名称类型是否相同并不重要,如x的int掩盖x的double. 现在引入继承,当派生类成员函数涉及基类的某物(成员函数,typedef,或成员变量),编译器可以找出所涉及的东西,派生继承了声明于基类的所有东西,实际运作方式是:Drived作用域嵌套于Base类作用域内。如下:class Base{private: int翻译 2015-03-11 12:02:43 · 395 阅读 · 0 评论 -
条款31:将文件间的编译依赖关系降至最低
假设修改calss的实现文件,不是修改class接口,只是实现中的private成分。然后你会发现全部编译和重新连接了。这些问题是因为C++没有“将接口从实现中分离”做得足够好。class的定义不只是描述了class接口,还包括实现细节。如下代码:class Person{ public: Person(conststring& nam翻译 2015-03-10 09:39:51 · 487 阅读 · 0 评论 -
条款40、明智地使用多重继承
现在考虑多重继承MI(multiple inheritance)。程序可能从一个以上的基类继承相同的名称(如函数 typedef等)。那会导致较多歧义。考虑以下代码:class A{ public: void fun();….};class B{ private: bool fun()const;};cla翻译 2015-03-21 15:06:25 · 442 阅读 · 0 评论 -
条款27:尽量少做转型动作
C++的设计目标之一是保证“类型错误”绝不会发生。但转型(cast)破坏了类型系统(type system).旧式转型,C风格的转型动作,如下: (T)tmp //将tmp转型为T T(tmp) //同上,函数风格的转型动作C++提供四种新式转型(new-style或C++-style casts) 1、const_cast (tmp) 通常用来将对象的翻译 2015-01-16 20:03:39 · 323 阅读 · 0 评论 -
第15条:在资源管理类提供对原始资源的访问
资源管理类可有效防止程序发生资源泄漏。能否预防此类泄漏是一个系统设计方案是否优异的一个基本评判标准。可以完美依靠资源管理类来完成所有与资源间的互动,但永远也不能直接访问原始资源。然而也并不完美。许多 API 会直接引用资源,所以除非你不使用这样的 API (显然不切实际),否则,只能绕过资源管理器访问原始资源(raw resources)。举例说,第 13 条中引入了下面的做法 :使用诸如au原创 2014-12-10 21:49:26 · 377 阅读 · 0 评论 -
第19条: 要像设计类型一样设计 class
与其它的面向对象编程语言类似,在 C++ 中,定义一个新的 class ,也就定义了一个新的type。一个 C++ 设计人员的大多数时间都会用在不断丰富充实他们的类型系统上(type system)。这意味着不仅仅是一个 class 的设计者,还是类型设计者。如重载函数和运算符、控制内存的分配和释放、定义对象的初始化和终止。因此应该和“语言设计者设计语言内置类型”一样的谨慎来对待类设计。翻译 2014-12-16 16:32:58 · 475 阅读 · 0 评论 -
第8条:防止异常逃离析构函数(prevent exceptions from leaving destructors)
C++ 并没有禁止析构函数引发异常,但 是 C++不推荐这样做。这是有理由的。如下代码:class Widget {public: ... ~Widget() { ... } // 假设它会引发一个异常};void doSomething(){ std::vector v; ...}// v 在这里被自动销毁 当 vector v 被销毁时,它也有责任销毁其所包含的所原创 2014-11-28 11:06:03 · 505 阅读 · 0 评论 -
第7条:将多态基类的析构函数声明为虚函数
考虑一个计时器,我们首先创建一个TimeKeeper 基类,然后在它的基础上创建各种派生类,用于不同方式的计时。 class TimeKeeper {public: TimeKeeper(); ~TimeKeeper(); ...}; class AtomicClock: public TimeKeeper { ...}; //原子钟class WaterClock:原创 2014-11-27 15:50:14 · 498 阅读 · 0 评论 -
第6条:显式禁止编译器为生成不必要的函数
房地产代理商的工作是卖房子,而一个为代理商提供支持的软件系统自然要用一个类来描述待出售的房屋:class HomeForSale { ... }; 就如房地产代理商能够很快指出每一间住宅都是独一无二——没有两间是完全一样的。既然如此,为一个 HomeForSale 对象复制出一个副本的想法就显得无意义了。怎么能够复制那些生来就独一无二的东西呢?如果你尝试去复制一个 HomeForSa原创 2014-11-26 15:37:01 · 409 阅读 · 0 评论 -
条款5、了解C++默默编写并调用了哪些函数
编写的每个类几乎都有一个或多个构造函数、一个析构函数和一个赋值运算符。这些是编写一个类所必需的一些函数,控制着类的基本操作,如产生对象并初始化,以及从系统中排除旧对象并对其进行恰当的清理工作,还有赋予对象新值。在这些函数中出错带来很大的负面影响,所以正确地写好这些函数是十分重要的。这些函数构成了类的中枢神经。这一章中将为你介绍怎样编写这些程序才会使你的类更加优秀。 在 C++ 处理过之后,原创 2014-11-26 11:14:55 · 646 阅读 · 0 评论 -
条款4、确定对象在使用前已被初始化
C++ 在将对象初始化问题上变化多端。比如说,你写下了下面的代码:int x; 在许多情况下, x 会确保得到初始化(为零),但是另一些情况下则不会,如果这样写: class Point { intx, y;};...Point p;p 的数据成员在某些情况下会被初始化(为零),但是另一些情况就不会了。如果你以前学习的语言没有对象初始化的概念,那么请你注意原创 2014-11-19 10:29:06 · 565 阅读 · 0 评论 -
第3条:尽可能使用 const
const的用处就是:可以通过它来指定一个语义上的约束(一个特定的不能被更改的对象)这由编译器来保证。通过一个const,可以让编译器和其他程序员知道,你的程序中有一个数值需要保持恒定不变。const用途十分广泛。可以在类外部修饰全局(global)的或者名字空间域(namespace)的常量,或修饰文件、函数、或区块作用域(block scope)中被声明为static的对象。你可以在灰内部原创 2014-11-17 16:25:32 · 516 阅读 · 0 评论 -
条款2 尽量用const,enum,inline代替#define
这个条款改为“尽量把工作交给编译器而不是预编译器”更恰当,因为或许#define不被视为语言的一部分,这正是问题所在,如下:#define DATA 1.6编译器也许根本就接触不到这个DATA符号名 ,可能它在编译器对源代码进行编译之前就被预处理器替换掉了。So, 这一名字很可能不会列在符号表(symbletabal)中,如果你在代码中使用了这常量遇到一些错误,却往往很难找到问题的所在原创 2014-11-16 16:35:00 · 481 阅读 · 0 评论 -
第11条: 在 operator= 中处理“自我赋值”
当对象对其自身赋值时,就发生了一次“自赋值”:class Widget { ... };Widget w;...w = w; // 自赋值这样做似乎没什么意义,但是合法,因此以后我们假设客户可能会这样做。而且,赋值工作本身并不总是那么容易辨认的。比如:a = a[j]; // 可能发生自赋值如果 i 和 j 的值相同,那么这就是一次自赋值。另外*px = *py; //原创 2014-12-03 12:13:25 · 566 阅读 · 0 评论 -
第10条: 让赋值运算符返回一个指向 *this 的引用
关于赋值,有趣的是可以把赋值操作连在一起:int x, y, z;x = y = z = 15; // 一连串赋值同样有趣的是:赋值采用右结合律,所以上述可以表示成:x = (y = (z = 15));在这里, 15 首 先赋 值给 z ,然后这次赋值的结果(就是更新过的 z 的值)将赋给 y ,然后这次赋值的结果(就是更新过的 y 的值)又赋给 x 。该实原创 2014-12-02 21:44:33 · 817 阅读 · 0 评论 -
第20条:尽量使用“引用常量”传参代替传值
默认情况C++以传值方式传递对象至函数(继承自C语言的特征)。除非另外指定,否则函数参数都是以实际实参的复件(副本)为初值,并且,函数调用者得到的也是函数返回值的一个副本。这些副本是由对象的拷贝构造函数创建。这使得“传值”成为昂贵的操作。考虑以下class继承体系:class Person {public: Person(); // 为简化省略参数表 virtual ~Pers翻译 2015-01-08 20:34:08 · 785 阅读 · 0 评论 -
第14条:留心资源管理类中的复制行为
第 13 条 中介绍了“资源获取即初始化”( Resource Acquisition Is Initialization ,简称 RAII )的概念,它是资源管理的主要内容。同时第 13 条 中 还使用 auto_ptr 和 tr1::shared_ptr 为示例,描述了这一概念是如何管理堆上的资源。然而并不是所有的资源都分配于堆上,对于不分配于堆上的资源,类似于 auto_ptr 和 tr1:原创 2014-12-09 14:31:19 · 587 阅读 · 0 评论 -
条款25:考虑写出一个不抛出异常的swap函数
swap原本只是STL的一部分,后面成为异常安全编程的脊柱,及处理自我赋值安全性的一个常见机制。例子:标准程序库提供的swap算法namespace s td{ templatevoid swap(T& a,T& b) //std::swap的典型实现{T tmp(a);a=b;b=tmp;}}要求:类型T支持copying(通过copying构造函数和copya翻译 2015-01-14 14:50:21 · 442 阅读 · 0 评论 -
第13条:以对象来管理资源
第三章 资源管理 资源就是:一旦借助它所做的事情完成,必须要将其返回给系统。如果不这样,糟糕的事情就会发生。C++ 程序中最常使用的资源就是动态分配内存(如果分配了内存但是却忘记释放,就会导致内存泄漏),但是内存只是你所需要管理的众多资源这一。其它常见的资源包括文件描述器(file descriptors)、互斥锁、以及图形界面( GUI)中的字体和画笔、数据库联接、网络套结字。无论是何种资源原创 2014-12-08 14:28:51 · 375 阅读 · 0 评论 -
第12条: 复制整个对象,不要遗漏任一部分
一个设计良好的面向对象系统中,对象的所有内在部分都会被封装起来,只有两个函数负责对象拷贝:即copy构造函数和拷贝赋值(copy assigment)运算符。我们称他们为拷贝函数。第 5 条中详细讲述了编译器将会在需要的时候自动生成拷贝函数,并说明这些“编译器生成版”的行为:把被拷贝对象的所有成员变量都做一份拷贝。 如果你声明自己的copying函数,你向编译器表明,默认实现方式中有一些内原创 2014-12-06 21:31:36 · 365 阅读 · 0 评论 -
条款24:若所有参数都需要类型转换,请为此采用non-member函数
如果你需要为某个函数的所有参数(包括this指针所的那个隐喻参数)进行类型转换,那么这个函数必须是non-member例子:class Rational {public: Rational(intnumerator=0, int denominator=1); //不是explicit,为了支持混合运算翻译 2015-01-12 19:45:16 · 764 阅读 · 0 评论 -
条款23、宁以non-menber、non-friend替换成员函数
考虑以下browser类:class WebBroswer{ public: ... voidclearCache(); voidclearHistory(); voidremoveCookies(); ...};很多人会想到提供一函数执行一系列操作:class WebBrows翻译 2015-01-11 15:20:21 · 744 阅读 · 0 评论 -
条款22、将成员变量声明为private
假设有一个public成员变量,当取消它时、所有使用它的客户代码都会被破坏。因此public成员变量完全没有封装性。 假设有一个protected成员变量,当取消它时、所有使用它的derived classes都会被破坏。 所有反对public成员变量的论点同样适用于protected。其封装性并不比public好 语法一致性:使用不同的访问权限可以使函数对成员变量的处理有更精确翻译 2015-01-11 10:55:55 · 523 阅读 · 0 评论