《Effectirve C++》笔记(条款21~40)

本文探讨了C++编程中的关键原则与最佳实践,包括对象返回、成员变量封装、成员函数设计、异常安全、转型使用、编译依赖、public继承、虚函数选择等,旨在帮助程序员提高代码质量和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

条款21:必须返回对象时,别妄想返回其reference

不要返回函数中new而存在于堆中对象的引用(不知道谁负责delete),不要返回函数局部变量的引用(栈中,这样函数结束实例已经被销毁)。不要反悔局部静态成员的引用,对于多线程不安全,对于连续调用行为也不可控,当真的需要返回一个对象时就返回对象而不是引用。

任何一个引用必然存在一个和他不同名的相同实例,不得不考虑另一个名字的行为。

条款22:将成员变量声明为private

protected并不比public更具封装性。数据成员的操作应具有一致性,public下无法保证调用者行为。

条款23:宁以non-member、non-friend替换member函数

可以把成员函数放到另一个class做为静态函数,以保证一个class就是对数据的封装,超出目的范围的提出来,保证封装性。

条款24:若所有参数皆需要类型转换,请为此采用non-member函数

能避免friend就避免,变成非成员函数以后若非必要不要加friend,朋友带来的麻烦往往多余价值。

条款25:考虑写出一个不抛出异常的swap函数

当swap对自定义类型效率不高时,应提供swap成员函数,并确保不发生异常。并提供一个非成员函数调用他。

对于建立的非成员函数,std命名空间中不能够扩充新的函数,重载也不行,对于函数的非成员swap要用全特化。template<> void swap(xx& a, xx& b){};

调用swap时神队std::swap使用using声明式,然后调用swap并不带任何命名空间资格修饰

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

尽量在用的时候建立对象,不要提前建立。

对于循环,除非明确知道复制成本小于“构造+析构”成本,并且正在处理效率高度敏感的代码,否则不要把变量初始化放到循环外面。

确实从来没考虑过赋值成本与构造+析构的大小问题,单纯的只考虑了提出循环避免构造+析构。。。。从来都是写到外面的

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

提倡使用四个C++风格转型,不要用T(a)/(T)a这样的C方式转型。

const_cast常量性转换,唯一有此能力的C++转型符号

dynamic_cast主要用来进行安全向下转型,从父类引用或指针向派生类转换。派生类转父类用static_cast就行。这个需要消耗重大的运行成本

reinterpret_cast低级转型,行为和结果取决于编译器,不可移植。

static_cast强制转型,但无法进行const、non-const转换

static_cast也不能去掉volatile、__unaligned

reinterpret_cast以二进制形式对原有对象进行重新解释(可进行无关类型的转换),可以把指针转换为int等等,对一段区域的二进制内容进行赋值构成新的值并赋予新的类型解释,不会对二进制上的内容做任何修改,可用于:

  • 从指针类型到一个足够大的整数类型
  • 从整数类型或者枚举类型到指针类型
  • 从一个指向函数的指针到另一个不同类型的指向函数的指针
  • 从一个指向对象的指针到另一个不同类型的指向对象的指针
  • 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
  • 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

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

对于const函数避免返回指针、引用、迭代器指向内部成员(包括const的指针引用迭代器),使一个const函数更像一个常量函数,并避免dangling handles虚吊号码牌。

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

异常安全函数即使发生异常也不会泄露资源或允许任何数据结构破坏。这样的函数分为三种保证:

基本型:发生时,程序内任何事物仍然保持在有效状态下,不破坏对象和数据结构。但当前状态无法预料。

强烈类型:发生时,程序状态不变。

不抛异常型:int aa() throw();告知它宗科正常运行,不是永远不抛异常,而是如果发生将是致命错误。

绝大部分都在基本和强烈之间。一个系统有一个函数不具备异常安全性则整个系统都不具备。

一个函数的异常安全性不会超过所调用函数的最低异常安全性级别。

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

inline应用在小型、被频繁调用的函数上,对后续调试过程、二进制升级更好也可降低代码膨胀问题。

不要因为是函数模板就一定要声明为inline。

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

1想依赖于声明式,而不是定义式。头文件中应以完全且仅有声明式的形势存在,无论是否涉及模板。也就是私有成员用指针,前置声明对应class并在源文件中include。

2进一步的将私有成员过多情况下可以构建一个针对此class的实现类,并在声明类中的private只提供一个实现类的指针(用智能指针),将所有需要涉及到多个私有成员的实现操作转移到实现类。

3还可以用接口类。

对于2,类似于Qt库的实现,每个公开调用的类只有一个私有成员类名+private类的指针。

对于3,接口类要求无构造、虚析构、其他全部纯虚函数。无数据成员。 谷歌C++编程规范(当然这部分说了不要前置声明任何std内容): 尽可能地避免使用前置声明。使用 #include 包含需要的头文件即可。

优点:

  • 前置声明能够节省编译时间,多余的 #include 会迫使编译器展开更多的文件,处理更多的输入。
  • 前置声明能够节省不必要的重新编译的时间。 #include 使代码因为头文件中无关的改动而被重新编译多次。

缺点:

  • 前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。
  • 前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其 API. 例如扩大形参类型,加个自带默认参数的模板形参等等。
  • 前置声明来自命名空间 std:: 的 symbol 时,其行为未定义。
  • 很难判断什么时候该用前置声明,什么时候该用 #include 。极端情况下,用前置声明代替 includes 甚至都会暗暗地改变代码的含义:
    // b.h:
    struct B {};
    struct D : B {}
    
    // good_user.cc:
    #include "b.h"
    void f(B*);
    void f(void*);
    void test(D* x) { f(x); }  // calls f(B*)

如果 #includeBD 的前置声明替代, test() 就会调用 f(void*) .

  • 前置声明了不少来自头文件的 symbol 时,就会比单单一行的 include 冗长。
  • 仅仅为了能前置声明而重构代码(比如用指针成员代替对象成员)会使代码变得更慢更复杂.

结论:

  • 尽量避免前置声明那些定义在其他项目中的实体.
  • 函数:总是使用 #include.
  • 类模板:优先使用 #include.

条款32:确定你的public继承塑模出is-a关系

条款33:避免遮掩继承而来的名称

继承时对基类替换只看名字,不看参数、类型。也就是派生类中一个函数会覆盖掉基类中多个同名的重载函数,同时派生类中一个数据对象会覆盖掉基类的其他类型的同名对象。

可以通过派生类中写using BaseClassName::funciton_nam;方式让基类的所有function_name的重载函数都可见。

话说所有函数不是都被重命名了么?命名过程考虑了函数名和参数列表,那这样了话重载的两个函数实际上不是一个名字,派生过程为什么会覆盖掉同名的所有重载函数?

条款34:区分接口继承和实现继承

public继承下派生类继承了基类所有接口。

纯虚函数:只继承了接口

常规虚函数:继承了接口和一个缺省实现,可以重写。

非虚函数:继承了接口和强制性实现。

非虚函数不应该重写,但可以有这个函数(不算重写了),意义不一样。最后生成的object中实际上没有存储派生类的非虚函数的指针(没再vptl中存储),不支持多态,对应的函数是在class中的代码段存储了。这样当用多态切换成基类时会直接去基类的代码段找实现(vptl中没有),也就是多态性质不存在。

条款35:考虑virtual函数以外的其他选择

找virtual函数替代方式,可以用NVI收发及Strategy设计模式。NVI是特殊形式的template method设计模式。缺点是函数移到了class外部成为非成员函数无法访问class的非public成员。

tr1::function行为像函数指针,但可接纳与指定目标签名式兼容的所有可调用物。

调用一个virtual函数是有成本的,要经过最少一次vptr跳转到vtbl然后找到对应的函数指针

条款36:绝不重新定义继承而来的non-virtual函数

34条补充写了。。。。

条款37:绝不要重新定义继承而来的缺省参数值

缺省函数值是静态绑定的,继承以后不应该再给任何一样/不一样的缺省值。

继承后重写只应该做动态绑定的事情。(就是vtbl中的函数指针指向的一个新的function object的实现)

条款38:通过复合塑模树has-a 或“根据某物实现出”

复合composition我还是喜欢说组合。。。public继承是is-a关系,在私有成员中包含其他class的object是组合。

is-implemented-in-terms of根据某实物出现

条款39:明智而审慎的使用private继承

private继承不是is-a关系。而是类似于组合的形势,内部可以调用base class,不会有外部公开的接口。尽可能用组合而不是private继承。相比于组合可以造成empty base最优化,对于对象尺寸较友好。

谷歌规范里好像禁止还是不建议private继承,总会有办法解决不需要base公开接口,但想调用base的protected接口的情况。

组合 > 实现继承 > 接口继承 > 私有继承

条款40:明智而审慎的使用多重继承

多重继承可能需要虚基类。。。就这个就可以尽可能拒绝了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值