条款26:尽可能延后变量定义式的出现时间

博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处!

有些对象,你可能过早的定义它,而在代码运行的过程中发生了导常,造成了開始定义的对象并没有被使用,而付出了构造成本来析构成本。

所以我们应该在定义对象时,尽可能的延后,甚至直到非得使用该变量前一刻为止,应该尝试延后这份定义直到可以给它初值实參为止。

这样做的优点是:不仅能够避免构造(析构)非必要对象,还能够避免无意义的default构造行为。

遇到循环怎么办?此时往往我们会有两个选择:

做法A:1个构造函数+1个析构函数+n个赋值操作 // 在循环外面定变量,在循环内赋值

做法B:n个构造函数+n个析构函数   // 在循环内定义并初始化变量

这时候要预计赋值的成本低还是构造+析构的成本低,另外值得考虑的是对象作用域的问题。

条款27:尽量少做转型动作

转型语法通常有三种不同的形式:

1,C风格的转型动作:(T)expression

2,函数风格的转型动作:T(expression)

3,上面的另外一种被称为“旧式转型”,C++提供了四种新式转型:

const_cast<T>(expression)  //通经常使用来将对象的常量性转除

dynamic_cast<T>(expression)  // 转换为子类,用来决定某对象是否归属继承体系中的某个类型,可是耗费重大执行成本

reinterpret_cast<T>(expression) // 运行低级转型,实际动作取决于编译器,这也就表示它不可移植。如将一个point to int 转型为一个int

static_cast<T>(expression)  // 强迫隐式转换

在非常多派生类的设计中,派生类的virtual函数须要去调用基类的virtual函数,以下是一个样例,window是一个基类,它定义了一个虚函数onResize,而Special Window是一个派生类。

// 博客地址:http://blog.csdn.net/cv_ronny/ 转载请注明出处!
class Window
{
public:
virtual void onResize();
}; class SpecialWindow :public Window
{
public:
virtual void onResize()
{
static_cast<Window>(*this).onResize(); // 将*this转型为Window,然后调用其onResize
// ... SpecialWindow专属动作
}
};

可是代码中的相通过转型来调用基类的virtual函数,其实static_cast<Window>(*this).onResize()调用的是一个*this的基类成份的一份拷贝的onResize函数,所以onResize操作所能影响到的成员仅仅属于一个暂时对象。解决的办法是把转型拿掉就可以,替换成Window::onResize()。

请记住:

假设能够,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。假设有个设计须要转型动作,试着发展无需转型的设计。

假设转型是必须要,试着将它隐藏于某个函数背后。客户随后能够调用该函数,而不需将转型放进他们自己的代码。

宁可使用C++style(新式)转型,不要使用旧式转型,前者非常easy被辨识,并且也比較角着分门别类的职掌。

条款28:避免返回handles指向对象的内部成分

class Point
{
public:
Point(int x, int y);
void SetX(int newVal);
void SetY(int newVal);
private:
int x_cor;
int y_cor;
}; struct RectData
{
Point ulhc; // 矩形左上角的点
Point lrhc; // 矩形右上角的点
};
class Rectangle
{
private:
shared_ptr<RectData> pData;
public:
Point& upperLeft()const{ return pData->lrhc; }
Point& lowerRight()const{ return pData->ulhc; }
};

上面的代吗中Point是表示坐标系中点的类,RectData表示一个矩形的左上角与右下角点的点坐标。Rectangle是一个矩形的类,包括了一个指向RectData的指针。

我们能够看到了uppLeft和lowerRight是两个const成员函数,它们的功能仅仅是想向客户提供两个Rectangle相关的坐标点,而不是让客户改动Rectangle。可是两个函数却都返回了references指向了private内部数据,调用者于是能够通过references更改内部数据。

这给了我们一些警示:成员变量的封装性仅仅等于“返回其reference”的函数的訪问级别;假设const成员函数传出一个reference,后者所指数据与对象自身有关联,而它又被存储于对象之外,那么这个函数的调用者能够改动那笔数据。

handles(号码牌,用于取得某个对象)指reference、指针和迭代器,它们返回一个“代表对象内部数据”的handle。

我们能够对上面的成员函数返回类型上加上const来解决这个问题:

public:
const Point& upperLeft()const{ return pData->lrhc; }
const Point& lowerRight()const{ return pData->ulhc; }

可是函数返回一个handle代表对象内部成分还总是危急的,由于可能会造成dangling handles(空悬的号牌)。比方某个函数返回GUI对象的外框(bounding box)。

class GUIObject{
//..
};
const Rectangle boundingBox(const GUIObject&obj);
GUIObject* pgo;
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());

对boundingBox的调用返回的是一个暂时对象,这个对象没有名称,随后我们调用了这个对象的upperLeft,返回了一个指向暂时对象内部数据的reference。可是在语句结束后,这个暂时对象会被销毁,pUpperLeft会变成空悬的、虚吊的(dangling)!

请记住

避免返回handles(包含reference、指针、迭代器)指向对象内部。遵守这个条款能够添加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”的可能性降至最低。

条款29:为“异常安全”而努力是值得的

异常安全函数即使发生异常也不会泄漏资源或同意不论什么数据结构败坏。这种函数区分为三种可能的保证:基本型、强烈型、不抛异常型。

强烈保证往往可以以copy-and-swap实现出来,但“强烈保证”并不是对全部函数都可以实现或具备现实意义。

函数提供的“异常安全保证”通常最高仅仅等于其所调用之各个函数的“异常安全保证”中最弱者。

条款30:透彻了解inlining的里里外外

关于inline:

1. inline函数的调用,是对函数本体的调用,是函数的展开,使用不当会造成代码膨胀。

2. 大多数C++程序的inline函数都放在头文件,inlining发生在编译期。

3. inline函数仅仅代表“函数本体”,并没有“函数实质”,是没有函数地址的。

值得注意的是:

1. 构造函数与析构函数往往不适合inline。由于这两个函数都包括了非常多隐式的调用,而这些调用付出的代价是值得考虑的。可能会有代码膨胀的情况。

2. inline函数无法随着程序库升级而升级。由于大多数都发生在编译期,升级意味着又一次编译。

3. 大部分调试器是不能在inline函数设断点的。由于inline函数没有地址。

请记住

1. 大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更easy,也能够使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。

2. 另外,对function templates的inline也要谨慎,保证其全部实现的函数都应该inlined后再加inline。

条款31:将文件间的编译依存关系降至最低

这个问题产生是源于希望编译时影响的范围尽量小,编译效率更高,维护成本更低,这一需求。

实现这个目标首先第一个想到的就是,声明与定义的分离,用户的使用仅仅依赖声明,而不依赖定义(也就是详细实现)。

但C++的Class的定义式却不只唯独接口,还有实现细目(这里指实现接口须要的私有成员)。而有时候我们须要改动的一般是接口的实现方法,而这一改动可能须要加入私有变量,但这个私有变量对用户是不应该可见的。但这一改动却放在了定义式的头文件里,从而造成了,使用这一头文件的全部代码的又一次编译。

于是就有了pimpl(pointer to implementation)的方法。用pimpl把实现细节隐藏起来,在头文件里仅仅须要一个声明就能够,而这个poniter则作为private成员变量供调用。

这里会有个有意思的地方,为什么用的是指针,而不是详细对象呢?这就要问编译器了,由于编译器在定义变量时是须要预先知道变量的空间大小的,而假设仅仅给一个声明而未定义的话是不知道大小的,而指针的大小是固定的,所以能够定义指针(即使仅仅提供了一个声明)。

这样把实现细节隐藏了,那么实现方法的改变就不会引起别的部分代码的又一次编译了。并且头文件里仅仅提供了impl类的声明,而主要的实现都不会让用户看见,也添加了封装性。

结构应该例如以下:

class AImpl;
class A {
public:
...
private:
std::tr1::shared_ptr<AImpl> pImpl;
};

这一种类也叫handle class

还有一种实现方法就是用带factory函数的interface class。就是把接口都写成纯虚的,实现都在子类中,通过factory函数或者是virtual构造函数来产生实例。

声明文件时这么写:

class Person
{
public:
static shared_ptr<Person> create(const string&,
const Data&,
const Adress&);
};

定义实现的文件这么写

class RealPerson :public Person
{
public:
RealPerson(...);
virtual ~RealPerson(){}
//...
private:
// ...
};

以上说的为了去耦合而使用的方法不可避免地会带上一些性能上的牺牲,但作者建议是发展过程中使用以上方法,当以上方法在速度与/或大小上的影响比耦合更大时,再写成详细对象来替换以上方法。

请记住:

支持“编译依存性最小化”的一般构想是:相依于声明式,不要相信于定义式。基于此构想的两个手段是Handles classes和Interface classes。

程序库头文件应该以“全然且仅有声明式”的形式存在,这样的做法不论是否涉及templates都适用。

Effective C++笔记05:实现的更多相关文章

  1. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  2. Effective Java笔记一 创建和销毁对象

    Effective Java笔记一 创建和销毁对象 第1条 考虑用静态工厂方法代替构造器 第2条 遇到多个构造器参数时要考虑用构建器 第3条 用私有构造器或者枚举类型强化Singleton属性 第4条 ...

  3. 《uml大战需求分析》阅读笔记05

    <uml大战需求分析>阅读笔记05 这次我主要阅读了这本书的第九十章,通过看这章的知识了解了不少的知识开发某系统的重要前提是:这个系统有谁在用?这些人通过这个系统能做什么事? 一般搞清楚这 ...

  4. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  5. [Effective JavaScript 笔记]第3章:使用函数--个人总结

    前言 这一章把平时会用到,但不会深究的知识点,分开细化地讲解了.里面很多内容在高3等基础内容里,也有很多讲到.但由于本身书籍的篇幅较大,很容易忽视对应的小知识点.这章里的许多小提示都很有帮助,特别是在 ...

  6. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  7. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  8. java effective 读书笔记

    java effective 读书笔记 []创建和销毁对象 静态工厂方法 就是“封装了底层 暴露出一个访问接口 ” 门面模式 多参数时 用构建器,就是用个内部类 再让内部类提供构造好的对象 枚举 si ...

  9. 强化学习读书笔记 - 05 - 蒙特卡洛方法(Monte Carlo Methods)

    强化学习读书笔记 - 05 - 蒙特卡洛方法(Monte Carlo Methods) 学习笔记: Reinforcement Learning: An Introduction, Richard S ...

随机推荐

  1. 基于Andoird 4.2.2的Account Manager源代码分析学习:创建选定类型的系统帐号

    AccountManager.addAccount() public AccountManagerFuture<Bundle> addAccount(final String accoun ...

  2. MSF 离线攻击

    MSF 离线攻击 MSF连环攻击在internet上实现是不太现实的,网络中的安全设备(防火墙.入侵检测.入侵防护系统). 实验拓扑如下: 实验说明:安全实验中的包过滤防火墙在测试中使用的是linux ...

  3. -bash: ./job.sh: /bin/sh^M: bad interpreter: 没有那个文件或目录

    昨天在windows下用写字板写了个shell脚本,使用winscp上传到linux上运行的时候发现运行不了,提示-bash: ./job.sh: /bin/sh^M: bad interpreter ...

  4. 使用Intent启动组件

    android应用程序的三大组件--Activities.Services.Broadcast Receiver,通过消息触发,这个消息就是Intent,中文又翻译为"意图"(我感 ...

  5. C++封装SQLite实例&lt;三&gt;

    前一篇博客中介绍的是怎样依据sqlite3_get_table()函数来获取一张表的内容,就是一股脑的把表中的内容所有存储起来放在一个一维数组中,这其中的规则已经介绍过了.接下来讲的是怎样依据一个SQ ...

  6. [置顶] Guava学习之Multimap

    相信大家对Java中的Map类及其之类有大致的了解,Map类是以键值对的形式来存储元素(Key->Value),但是熟悉Map的人都知道,Map中存储的Key是唯一的.什么意思呢?就是假如我们有 ...

  7. 去掉word中向下的箭头^l----->^p

    去掉word中向下的箭头 在网页上复制文章到word中,会发现有很多向下的箭头,这些 符号叫做软回车符.如何去掉这些向下的箭头呢.步骤如下: 方法/步骤 按Ctrl+H,弹出全局替换窗口,输入查找内容 ...

  8. 关于Hbase的cache配置

    关于Hbase的cache配置 在hbase中的hfilecache中,0.96版本号中新添加了bucket cache, bucket cache通过把hbase.offheapcache.perc ...

  9. 使用Hamcrest增强JUnit的测试能力

    package com.jadyer.service; import java.util.HashMap; import java.util.Map; import org.hamcrest.Matc ...

  10. Redis安装及简单測试

    摘要: Redis是眼下业界很受到欢迎的一个内存数据库,一般用作系统的中间缓存系统,用以提升总体商业系统的吞吐量和响应速度.本文将简要介绍安装的主要过程以及给出一个简要的測试代码. 1.  系统环境和 ...