effective c++ 笔记 条款26-31

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

应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止,以此避免构造(和析构)非必要对象,还可以避免无意义的default构造行为
对于循环操作,在循环前还是中进行构造,取决于赋值操作与构造+析构操作的成本对比。循环前构造会将变量的作用域扩大,除非知道该变量的赋值成本比“构造+析构”成本低,或者对这段程序的效率要求非常高,否则建议使用循环中构造

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

const_cast:通常被用来将对象的常量性转除
dynamic_cast:主要用来执行“安全向下转型”,要用于基类和派生类之间的类型转换,具有类型检查的功能,比static_cast安全。运行时转型,但开销大,会执行对继承体系的检查
reinterpret_cast:意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植
static_cast:用来强迫隐式转换,编译时进行类型转换,主要用于基本数据类型的转换、隐式转换的显式化和向上转型
尽量避免转型,特别是在注重效率的代码中避免 dynamic_casts。
如果有个设计需要转型动作,通常两种做法,一:使用容器,并在其中存储直接指向derived class对象的指针(通常是智能指针),这样就避免了上述需求。二:在base class内提供virtual函数做你想对各个派生类想做的事情。这样可以使得你通过base class 接口处理“所有可能之各种派生类”。
如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内
宁可使用C++新式转型,也不用用C的旧式,因为新式的更容易被注意到,而且各自用途专一

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

reference、指针、迭代器系统都是所谓的handles。函数返回一个handle,随之而来的便是“降低对象封装性”的风险。它也可能导致:虽调用const成员函数却造成对象状态被更改的风险。

Point& UpperLeft() const { return pData->ulhc; }
Point& LowerRight() const { return pData->lrhc; }

成员变量可以在外部被修改,违反 logical constness 的原则的。绝对不应该令成员函数返回一个指针指向“访问级别较低”的成员函数。
改成返回常引用可以避免对成员变量的修改

const Point& UpperLeft() const { return pData->ulhc; }
const Point& LowerRight() const { return pData->lrhc; }

但是依然会带来一个称作 dangling handles(空悬句柄) 的问题,当对象不复存在时,你将无法通过引用获取到返回的数据
最保守的做法,返回一个成员变量的副本

Point UpperLeft() const { return pData->ulhc; }
Point LowerRight() const { return pData->lrhc; }

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

异常安全函数(Exception-safe functions)提供以下三个保证之一:

  1. 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下,没有任何对象或数据结构会因此败坏,所有对象都处于一种内部前后一致的状态,然而程序的真实状态是不可知的,也就是说客户需要额外检查程序处于哪种状态并作出对应的处理。
  2. 强烈保证: 如果异常被抛出,程序状态完全不改变,换句话说,程序会回复到“调用函数之前”的状态。
  3. 不抛掷(nothrow)保证: 承诺绝不抛出异常,因为程序总是能完成原先承诺的功能。作用于内置类型身上的所有操作都提供 nothrow 保证。
    throw() C++11 中已经被弃用,取而代之的是noexcept关键字:
int DoSomething() noexcept;

当异常抛出时,1.不泄漏任何资源 2.不允许数据败坏;

void PrettyMenu::ChangeBackground(std::vector<uint8_t>& imgSrc) {
    lock(&mutex);
    delete bgImage;
    ++imageChanges;
    bgImage = new Image(imgSrc);
    unlock(&mutex);
}

若在new Image函数中抛出异常,mutex会发生资源泄漏,未执行unlockbgImageimageChanges也会发生数据败坏,bgImage指向已删除对象

为做到强烈保证:

  1. 使用“资源管理类”条款13
  2. 调换代码顺序
void PrettyMenu::ChangeBackground(std::vector<uint8_t>& imgSrc) {
    Lock m1(&mutex);
    bgImage.reset(std::make_shared<Image>(imgSrc));
    ++imageChanges;
}

删除动作只发生在新图像创建成功后,只有make_shared成功,reset才调用,delete也只在reset函数内使用

另一个常用于提供强烈保证的方法是copy and swap,为打算修改的对象做出一份副本,对副本执行修改,并在所有修改都成功执行后,用一个不会抛出异常的swap方法将原件和副本交换

void PrettyMenu::ChangeBackground(std::vector<uint8_t>& imgSrc) {
    Lock m1(&mutex);
    auto pNew = std::make_shared<PMImpl>(*pImpl);    // 获取副本
    pNew->bgImage.reset(std::make_shared<Image>(imgSrc));
    ++pNew->imageChanges;
    std::swap(pImpl, pNew);
}

强烈保证并非对所有函数都可实现或具备现实意义

异常安全保证具有木桶效应

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

inlining函数可以免除函数调用成本的开销
inlining两种实现方式:一种是为其显式指定inline关键字,另一种是直接将成员函数的定义式写在类中
inline函数通常一定被置于头文件内,因为inlining大部分情况下都是编译期行为;template通常也被置于头文件内,因为大部分建置环境都是在编译期完成具现化动作;如果一个template具现出来的函数都应该inlined,则将此template声明为inline,否则应避免此声明
inline只是对编译器的一个申请,不是强制命令。大多数编译器如无法将要求的函数inline化,会给出一个警告信息
将大多数inlining限制在小型、被频繁调用的函数身上,以便于日后的调试和二进制升级
编译器通常不对“通过函数指针而进行的调用”实施inlining
构造函数和析构函数并不适合用于inlining,往往会引起代码的膨胀(即不要随便将构造函数和析构函数的定义体放在类声明中)
inline函数代码如发生改变,所有用到该inline函数的程序都必须重新编译
inline修饰符用于解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题
在现在的 C++ 标准中,inline作为优化建议的含义已经被完全抛弃,取而代之的是“允许函数在不同编译单元中多重定义”,使得可以在头文件中直接给出函数的实现(每个.cpp的编译都是独立的,对于每个cpp来说,都是包含了Func的声明和实现,所以在链接时不清楚到底是链接哪一个同名函数)
头文件里面定义全局变量或实现了具体的函数会报错“fatal error LNK1169: 找到一个或多个多重定义的符号”,类、结构体、函数声明等抽象的东西不会

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

为了增加编译速度,应该减少类文件之间的相互依存性(include),但是类内又常常使用到其他类,不得不相互依存
解决方案是:将类的声明和定义分开(不同的头文件),声明相互依存,而定义不相依存,这样当定义需要变更时,编译时不需要再因为依赖而全部编译。即尽可能让头文件自我满足
两种方法:
Handle classes/句柄类:一个声明类,一个imp实现类,声明类中不涉及具体的定义,只有接口声明,在定义类中include声明类,而不是继承。handle类的成员函数必须通过imp指针取得对象数据,每次访问会增加一层间接性会增加开销。且imp指针必须初始化在handle类的构造函数内,指向一个动态分配得来的imp对象,指针大小和动态分配内存也会带来开销
Interface classes/接口类:在接口类中提供纯虚函数,作为一个抽象基类,定义类作为其子类来实现具体的定义。增加存储虚表指针和实现虚函数跳转带来的开销

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值