读书笔记——Effective C++】的更多相关文章

1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C++标准预计在2009年被发布(虽然所有的工作很有可能在2007年底被完成).直到现在,发布下一版C++的预计年份还没有被确定,这就解释了为什么人们把下一版C++叫做“C++0x”——C++的200x年版本. C++0x可能会包含一些有趣的新的语言特性,但是大多数新C++功能将会以标准库附加物的形式被…
1.让自己习惯C++ 条款01:视C++为一个语言联邦 C++高效编程守则视状况而变化,取决于你使用C++的哪一部分. 条款02:尽量以const.enum.inline替换 #define 对于单纯常量,最好以const对象或enum替换#define 对于形似函数的宏,最好改用inline函数替换#define 条款03:尽可能使用const 将某些东西声明为const可帮助编译器侦测出错误用法.const可被施加于任何作用域内的对象.函数参数.函数返回类型.成员函数本体. 编译器强制实施b…
Item4 确保对象被使用前进行初始化 C++在对象的初始化上是变化无常的,例如看下面的例子: Int x; 在一些上下文中,x保证会被初始化成0,在其他一些情况下却不能够保证.看下面的例子: Class Point { Int x,y; }; Point p; P的数据成员有时候保证能够被初始化(成0),有时候却不能.如果你从不存在未初始化对象的语言中转到c++, 就需要注意了,因为这很重要. 使用未初始化对象的坏处 读取未初始化的值会产生未定义的行为.在一些平台中,仅仅读取未初始化的值就会让…
1 编译器会默认生成哪些函数  什么时候空类不再是一个空类?答案是用c++处理的空类.如果你自己不声明,编译器会为你声明它们自己版本的拷贝构造函数,拷贝赋值运算符和析构函数,如果你一个构造函数都没有声明,编译器同样会为你声明一个默认拷贝构造函数.这些所有的函数会是public和inline的(Item30).因此,如果你写了下面的类: 1 class Empty{}; 本质上来说和下面的类是一样的: 1 class Empty { 2 3 public: 4 5 Empty() { ... }…
Const可以修饰什么?   Const 关键字是万能的,在类外部,你可以用它修饰全局的或者命名空间范围内的常量,也可以用它来修饰文件,函数和块作用域的静态常量.在类内部,你可以使用它来声明静态或者非静态的数据成员.对于指针来说,你可以指定指针本身是不是const,指针指向的数据是不是const,两者可以同时为const或者两者同时为非const. Char greeting[]="Hello"; Char *p = greeting;//non-const pointer non-c…
这个条目叫做,尽量使用编译器而不要使用预处理器更好.#define并没有当作语言本身的一部分. 例如下面的例子: #define ASPECT_RATIO 1.653 符号名称永远不会被编译器看到.它可能在源码到达编译器之前被预处理器移除.ASPECT_RATIO 最终不会进入符号表,如果因为这个常量的使用而导致编译错误,会使你非常迷惑,因为错误信息会指向1.653而不是ASPECT_RATIO.如果ASPECT_RATIO被定义在一个不是你自己写的头文件中,你会不知道1.,653来自哪里.,…
Item 1 将c++视为一个语言联邦 如今的c++已经是一个多重泛型变成语言.支持过程化,面向对象,函数式,泛型和元编程的组合.这种强大使得c++无可匹敌,却也带来了一些问题.所有“合适的”规则看上去都有例外.我们怎样理解这样一门语言? 最容易的方法是不要将其看成单一的一门语言而是将其看成是一个有相关性的语言的联邦.在一个特定的子语言中,一些规则就比较简单,明确并且容易记忆.当你从一个子语言切换到另外一个子语言时,这些规则可能会改变.为了更好的理解c++,你必须识别主要的子语言,幸运的是,只有…
问题描述-阻止对象的拷贝 现实生活中的房产中介卖房子,一个服务于这个中介的软件系统很自然的会有一个表示要被销售的房屋的类: class HomeForSale { ... }; 每个房产中介会立刻指出来,要销售房屋的每个属性都是唯一的,没有两个完全一样的房屋.在这种情况下,拷贝一个HomeForSale对象就没有任何意义了.你在怎么能拷贝一些独一无二的东西呢?因此你可能会尝试,如果有拷贝HomeForSale对象的函数,代码将不能够通过编译. HomeForSale h1; HomeForSal…
1.为什么c++不喜欢析构函数抛出异常 C++并没有禁止析构函数出现异常,但是它肯定不鼓励这么做.这是有原因的,考虑下面的代码: class Widget { public: ... ~Widget() { ... } // assume this might emit an exception }; void doSomething() { std::vector<Widget> v; ... } // v is automatically destroyed here 当vector V被…
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为这是c++同它们不一样的地方. 假设你已经有一个为股票交易建模的类继承体系,它可以买卖股票等.这些交易的可审计性很重要,所以每次交易对象被创建的时候,需要在审计日志中创建一个合适的记录.这看上去是解决问题的合理方法: class Transaction { // base class for all…
1.自我赋值是如何发生的 当一个对象委派给自己的时候,自我赋值就会发生: class Widget { ... }; Widget w; ... w = w; // assignment to self. 这看上去是愚蠢的,但这是合法的,所以请放心,客户端是可以这么做的.此外,自身赋值也并不总是很容易的能够被辨别出来.举个例子: a[i] = a[j]; // potential assignment to self 上面的代码在i和j相等的情况下就是自我赋值,同样的,看下面的例子: *px =…
1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我们得知,如果需要的话编译器会为你生成这两个拷贝函数,并且编译器生成的版本能够精确的做到你想做的:它们拷贝了对象的所有数据. 2.自己实现构造函数有可能出现问题 当你声明自己的拷贝函数的时候,你就会向编译器表示,你对编译器生成版本的拷贝函数有些地方不是很喜欢.编译器看上去生气了,它们会以一种奇怪的方式…
1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... }; // root class of hierarchy of // investment types 进一步假设这个程序库通过一个工厂函数(Item 7)来给我们提供特定Investment对象: Investment* createInvestment(); // return ptr to…
1. 自己实现一个资源管理类 Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如何用堆资源来表现这个概念的.然而并不是所有资源都是在堆上创建的,对于这种资源,像auto_ptr和tr1::shared_ptr这样的智能指针就不适合当作资源句柄(handle)来使用了.你会发现你时不时的就会需要创建自己的资源管理类. 举个例子,假设你正在使用C API来操纵Mutex类型的互斥信…
1.为什么需要访问资源管理类中的原生资源  资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不要直接访问原生(raw)资源而玷污你的双手.但是世界不是完美的,许多API会直接引用资源,所以除非你放弃使用这样的API(这是不实际的想法),你将会绕开资源管理类而时不时的处理原生资源. 2. 如何获取原生资源——通过显示转换和隐式转换 2.1 一个例子 举个例子,Item 13中介绍了使用像aut…
1. 一个错误释放内存的例子 下面的场景会有什么错? std::]; ... delete stringArray 一切看上去都是有序的.new匹配了一个delete.但有一些地方确实是错了.程序的行为是未定义的.至少来说,stringArray指向的100个string对象中的99个看上去都不能被正确释放,因为他们的析构函数可能永远不会被调用. 2. 使用new 和delete时究竟做了啥? 当你使用一个new表达式(通过使用new动态的创建一个对象)时,会发生两件事情.第一,内存被分配(通过…
1. 可能会出现资源泄漏的一种用法 假设我们有一个获取进程优先权的函数,还有一个在动态分类的Widget对象上根据进程优先权进行一些操作的函数: int priority(); void processWidget(std::tr1::shared_ptr<Widget> pw, int priority); 注意这里使用了对象管理资源的用法(Item 13),processWidget为它需要处理的动态分配对象Widget使用了智能指针(tr1::shared_ptr). 现在考虑对proc…
1. 什么样的接口才是好的接口 C++中充斥着接口:函数接口,类接口,模板接口.每个接口都是客户同你的代码进行交互的一种方法.假设你正在面对的是一些“讲道理”的人员,这些客户尝试把工作做好,他们希望能够正确使用你的接口.在这种情况下,如果接口被误用,你的接口应该至少负一部分的责任.理想情况下,如果使用一个接口没有做到客户希望做到的,代码应该不能通过编译:如果代码通过了编译,那么它就能做到客户想要的. 2. 编写好的接口的方法列举 2.1 使接口不容易被误用——通过引入新的类型 开发出容易被正确使…
1. 你需要重视类的设计 c++同其他面向对象编程语言一样,定义了一个新的类就相当于定义了一个新的类型(type),因此作为一个c++开发人员,大量时间会被花费在扩张你的类型系统上面.这意味着你不仅仅是一个类的设计者同时是一个类型设计者.重载函数和运算符,控制内存分配和释放,定义对象初始化和终结,这些都是你手里的事情.因此你应该同语言设计者一样,它们将时间浪费在内建类型的设计上,你就应该对类的设计施以同样的关注. 2. 高效的类型的特征 设计好的类很具有挑战性,因为设计好的类型具有挑战性.好的类…
1. 继承体系中关于对象释放遇到的问题描述 1.1 手动释放 关于时间记录有很多种方法,因此为不同的计时方法创建一个TimeKeeper基类和一些派生类就再合理不过了: class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); ... }; class AtomicClock: public TimeKeeper { ... }; class WaterClock: public TimeKeeper { ... }; class Wrist…
1. 问题的提出:要求函数返回对象时,可以返回引用么? 一旦程序员理解了按值传递有可能存在效率问题之后(Item 20),许多人都成了十字军战士,决心清除所有隐藏的按值传递所引起的开销.对纯净的按引用传递(不需要额外的构造或者析构)的追求丝毫没有懈怠,但他们的始终如一会产生致命的错误:它们开始传递指向并不存在的对象的引用.这可不是好事情. 考虑表示有理数的一个类,它包含将两个有理数相乘的函数: class Rational { public: Rational(, // see Item 24…
我们首先看一下为什么数据成员不应该是public的,然后我们将会看到应用在public数据成员上的论证同样适用于protected成员.最后够得出结论:数据成员应该是private的. 1. 为什么数据成员不能是public的? 为什么数据成员不能够是public的? 2.1 一致性 让我们从句法的一致性开始(Item 18).如果数据成员不是Public的,那么客户访问对象的唯一方法就是通过成员函数.如果所有的公共接口都是函数,客户就不必记住访问一个类的成员时是否使用括号了.这方便了客户的使用…
1. 非成员非友元好还是成员函数好? 想象一个表示web浏览器的类.这样一个类提供了清除下载缓存,清除URL访问历史,从系统中移除所有cookies等接口: class WebBrowser { public: ... void clearCache(); void clearHistory(); void removeCookies(); ... }; 许多用户想将这些动作一块执行,所以web浏览器为此可以提供一个函数: class WebBrowser { public: ... void…
1. 将需要隐式类型转换的函数声明为成员函数会出现问题 使类支持隐式转换是一个坏的想法.当然也有例外的情况,最常见的一个例子就是数值类型.举个例子,如果你设计一个表示有理数的类,允许从整型到有理数的隐式转换应该是合理的.在C++内建类型中,从int转换到double也是再合理不过的了(比从double转换到int更加合理).看下面的例子: class Rational { public: Rational(, // ctor is deliberately not explicit; ); //…
1. swap如此重要 Swap是一个非常有趣的函数,最初作为STL的一部分来介绍,它已然变成了异常安全编程的中流砥柱(Item 29),也是在拷贝中应对自我赋值的一种普通机制(Item 11).Swap非常有用,恰当的实现swap是非常重要的,与重要性伴随而来的是一些并发症.在这个条款中,我们将探索这些并发症以及如何处理它们. 2. swap的傻瓜实现方式及缺陷 2.1 swap函数的默认实现 Swap函数就是将两个对象的值进行交换,可以通过使用标准的swap算法来实现: namespace…
一个关于赋值的有趣的事情是你可以将它们链在一起: int x, y, z; x = y = z = ; // chain of assignments 同样有趣的是赋值采用右结合律,所以上面的赋值链被解析成下面这个样子: x = (y = (z = )); 在这里,15被赋值给z,然后赋值的结果(更新的z)被赋值给y,再然后赋值的结果(更新的Y)被赋值给x. 实现这个赋值链的方法是使赋值返回指向左边参数的引用,这也是你在为你的类实现赋值运算符的时候应该遵守的约定: class Widget {…
1. 定义变量会引发构造和析构开销 每当你定义一种类型的变量时:当控制流到达变量的定义点时,你引入了调用构造函数的开销,当离开变量的作用域之后,你引入了调用析构函数的开销.对未使用到的变量同样会产生开销,因此对这种定义要尽可能的避免. 2. 普通函数中的变量定义推迟 2.1 变量有可能不会被使用到的例子 你可能会想你永远不会定义未使用的变量,你可能要再考虑考虑.看下面的函数,此函数返回password的加密版本,提供的password需要足够长.如果password太短,函数会抛出一个logic…
异常安全在某种意义上来说就像怀孕...但是稍微想一想.在没有求婚之前我们不能真正的讨论生殖问题. 假设我们有一个表示GUI菜单的类,这个GUI菜单有背景图片.这个类将被使用在多线程环境中,所以需要mutex进行并发控制. class PrettyMenu { public: ... void changeBackground(std::istream& imgSrc); // change background ... // image private: Mutex mutex; // mute…
最近北京房价蹭蹭猛涨,买了房子的人心花怒放,没买的人心惊肉跳,咬牙切齿,楼主作为北漂无房一族,着实又亚历山大了一把,这些天晚上睡觉总是很难入睡,即使入睡,也是浮梦连篇,即使亚历山大,对C++的热情和追求还是不减,应该是感动了周公吧,梦境从此处开始,大师入场来给我安慰了... 11点躺在床上了,脑子里总结一下最近的工作:最近的开发用到inline函数比较多,众所周知,inline的使用是为了提高程序性能,可结果却总不尽如人意,这个捉急啊,嗯?怎么突然到了山脚下,周边树木林立,郁郁葱葱,鸟儿委婉啼叫…
1. 牵一发而动全身 现在开始进入你的C++程序,你对你的类实现做了一个很小的改动.注意,不是接口,只是实现:一个私有的stuff.然后你需要rebuild你的程序,计算着这个build应该几秒钟就足够了.毕竟,只修改了一个类.你点击了build 或者输入了make( 或者其他方式),你被惊到了,然后羞愧难当,因为你意识到整个世界都被重新编译和重新链接了!当这些发生时你不觉的感到愤恨么? 2. 编译依赖是如何发生的 问题出在C++并不擅长将接口从实现中分离出来.类定义不仅指定了类的接口也同时指定…