![](https://img-blog.csdnimg.cn/2019092715111047.png?x-oss-process=image/resize,m_fixed,h_224,w_224)
Effective C++
EffectiveC++书籍的读后感
ccloud11
分享技术就如同分享快乐一样,独乐乐不如众乐乐。
展开
-
条款32:确定你的public继承塑模出 is-a 关系
如果你编写类D(“派生类”)public继承类B(“基类”),就是在告诉C++编译器(以及代码的读者)每个类型D的对象都是类型B的对象,但反之则不然。public继承意味着“is-a”。适用于基类的所有内容也必须适用于派生类,因为每个派生类对象都是基类对象。public继承和is-a的等价关系听起来很简单,但有时你的直觉可能会误导你。类Square应该public继承类Rectangle吗?原创 2024-01-30 21:50:13 · 206 阅读 · 0 评论 -
条款31:将文件间的编译依存关系降至最低
C++在分离接口和实现方面做得不好。类定义不仅指定了类接口,还指定了相当多的实现细节。如果这些头文件发生了任何变化,或者它们依赖的任何头文件发生了变化,则必须重新编译所有包含Person类的文件。原创 2024-01-28 11:00:55 · 335 阅读 · 0 评论 -
条款30:透彻了解 inlining 的里里外外
内联函数背后的思想是用它的代码体替换对该函数的每次调用,大多数是编译时的行为。这可能会增加目标代码的大小(如果函数特表小还可能减少目标代码的大小)。它看上去,用起来都是函数,还避免了起函数调用的开销。如果函数过于复杂,编译器会拒绝内联请求(例如,那些包含循环或递归的函数);由于编译时无法确定,所有的虚函数都不允许内联。构造基类部分、构造成员、如果在构造对象的过程中抛出异常,对象中任何已经完全构造好的部分都会自动被销毁。内联是对编译器的请求,而不是命令。构造函数和析构函数通常不适合内联。原创 2024-01-27 15:47:45 · 241 阅读 · 0 评论 -
条款29:为“异常安全”而努力是值得的
如果Image构造函数抛出异常,输入流的read标记可能被移动了,而这种移动对程序的其他部分来说是可见的状态变化。在changeBackground解决这个问题之前,它只能提供异常安全性的基本承诺。下面的代码改变了bgImage的类型,使用智能指针不但可以防止资源泄漏,还提供了异常安全性;2、强烈保证:如果抛出异常,程序的状态不会改变(要么全部成功,要么就像没发生过)。从异常安全性的角度来看,这个函数很糟糕。异常安全性有两个要求,这里都不满足。1、基本承诺:如果抛出异常,程序中的所有内容都保持有效状态。原创 2024-01-21 21:53:17 · 338 阅读 · 1 评论 -
条款28:避免返回 handles 指向对象的内部成分
即便如此,upperLeft和lowerRight仍然会返回对象内部的句柄,这在其他方面可能会造成问题。特别是,它可能导致悬空(dangling )句柄:指向不再存在的对象的句柄。避免返回指向对象内部的句柄(引用、指针或迭代器)。遵守这个原则将会增加了封装性,帮助const成员函数保持const行为,并可以尽量避免发生悬空句柄的创建。这个设计可以编译,但它是错误的。我们设计的类返回的是一个const修饰的对象,但是我们还是可以修改内部对象的值。原创 2024-01-21 16:49:38 · 354 阅读 · 0 评论 -
条款27:尽量少做转型动作
前面的方法不允许将指向多种派生类的指针存储在同一个容器中。这样的C++生成的代码既大又慢,而且很脆弱,因为每次Window类层次结构发生变化时,都必须检查所有这些代码以确定是否需要更新。旧式强制转换仍然是合法的,但新风格更可取;在C++中,强制转换是一种需要高度重视的功能。首先回顾一下强制转换的语法,相同的强制转换通常有三种不同的方式。dynamic_cast的开销较大,深层的层次结构或使用多重继承的层次结构更加昂贵。有时,两个指针的值并不相同,需要使用偏移量,在运行时应用于Derived。原创 2024-01-16 22:36:32 · 465 阅读 · 0 评论 -
条款26:尽可能延后变量定义式出现的时间
尽可能推迟变量定义。它增加了程序的清晰度,提高了程序的效率。未使用的变量是有开销的,所以你应该尽可能避免使用它们。开销:1个构造函数+ 1个析构函数+ n个赋值。开销:n个构造函数+ n个析构函数。原创 2024-01-16 22:17:22 · 388 阅读 · 0 评论 -
条款25:考虑写出一个不抛异常的swap函数
C++11之前,std::swap是通过标准的交换算法完成的(C++ 11之后用的是std::move,并明确申明为noexcept)。一般来说,重载函数模板是可以的,但std是一个特殊的命名空间。这样的行为是未定义的,虽然大多数情况下可以正常编译。C++允许类模板的偏特化,但不允许函数模板的偏特化。要交换两个Widget对象的值,真正需要做的就是交换它们的pImpl指针,但默认的交换算法无法知道这一点。它试图访问a和b中的pImpl指针,它们是私有的,所以无法通过编译!原创 2024-01-15 22:06:44 · 513 阅读 · 0 评论 -
条款24:若所有参数皆需类型转换,请为此采用非成员函数
乘法应该是可交换的,但是由于参数的隐式类型转换只发生在参数列表中,所以2 * oneHalf会报错,由于2是int类型的参数。重载函数修改为非成员函数,编译器还会寻找非成员运算符。这时候我们可以将operator。原创 2024-01-15 21:51:22 · 364 阅读 · 0 评论 -
条款23:宁以 non-member、non-friend 替换 member 函数
优先选择非成员非友元函数而不是成员函数。这样做可以提高封装性、打包灵活性和功能可扩展性。原创 2024-01-13 11:04:49 · 345 阅读 · 0 评论 -
条款22:将成员变量声明为 private
更重要的是为了封装!如果你通过函数实现了对数据成员的访问,那么以后可以替换,使用者并不会察觉。将数据成员隐藏在函数接口之后可以提供各种实现灵活性。如何实现averageSoFar函数?原创 2024-01-10 22:19:10 · 340 阅读 · 0 评论 -
条款21:必须返回对象时,别妄想返回其引用
绝不要返回指针或引用指向一个local stack对象,或返回引用指向一个heap-allocated对象,或返回指针或对象指向一个local static对象而有可能同时需要多个这样的对象。也许你会想到一个基于operator*的实现,它返回一个定义在函数内部的静态Rational对象的引用。这里还是发生了构造和析构,更糟糕的是引用的对象被析构了。结果也是错误的,由于操作的都是同一块内存,所以执行后的结果都是最后一次执行的结果。让我们考虑在堆上构造一个对象并返回它的引用的可能性。原创 2024-01-10 22:12:35 · 536 阅读 · 0 评论 -
条款20:宁以常量引用传递替换值传递
通过引用传递参数也可以避免切割问题(slicing problem)。当继承类对象作为基类对象(按值)传递时,基类的拷贝构造函数会被调用,而那些派生类对象特有的部分会被“切掉”。调用了Student拷贝构造函数来初始化形参s。当validateStudent返回时,形参s被销毁。值传递的开销可能比较大。// 调用,被切割,而且没有多态。原创 2024-01-08 22:48:24 · 445 阅读 · 0 评论 -
条款19:设计class犹如设计type
【代码】条款19:设计class犹如设计type。原创 2024-01-08 22:36:24 · 331 阅读 · 0 评论 -
条款18:让接口容易被正确使用,不易被误用
shared_ptr可以解决“跨DLL问题”。当在一个DLL中使用new创建对象,但在另一个DLL中delete时,会出现此问题。shared_ptr避免了这个问题,因为它的默认删除器来自创建shared_ptr的同一个DLL中。只有12个有效的月份值,Month类型应该反映这一点。一种方法是使用枚举来表示月份,但枚举的类型安全性不高。例如,枚举可以像int一样使用(参见条款2)。这会导致至少两种类型的客户错误:没有删除指针,以及多次删除同一个指针。原创 2024-01-07 17:05:19 · 584 阅读 · 0 评论 -
条款17:以独立语句将 new出来的对象置入智能指针
在独立语句中将新对象存储在智能指针中。如果不这样做,在抛出异常时可能会导致微妙的资源泄漏。原创 2024-01-03 22:25:00 · 383 阅读 · 0 评论 -
条款16:成对使用 new 和 delete 时要采用相同形式
对于喜欢使用宏typedef的人来说,这条规则也值得注意,因为它意味着当使用new来生成typedef类型的对象时,必须说明应该使用哪种形式的delete。内存数组通常包括数组的大小,delete可以知道需要调用多少个析构函数。下面程序的行为是未定义的。至少,stringArray指向的100个string对象中有99个不太可能被正确地析构。使用delete时使用了方括号,delete假定指向的是一个数组。为了避免这种混淆,请避免对数组类型使用typedef,可以使用vector替代。原创 2024-01-03 22:11:17 · 418 阅读 · 0 评论 -
条款15:在资源管理类中提供对原始资源的访问
经常有API需要访问原始资源,因此每个RAII类都应该提供方法来获取它管理的资源。访问可以通过显式转换或隐式转换。一般来说,显式转换更安全,而隐式转换对客户端来说更方便。原创 2024-01-02 22:14:38 · 386 阅读 · 0 评论 -
条款14:在资源管理类中小心拷贝行为
3.复制底部资源:有时可以有任意多个资源的副本,需要资源管理类的唯一原因是确保每个副本都能被正确释放。在这种情况下,复制资源管理对象也应该复制它包裹的资源。2、引用计数底层资源:保留资源,直到使用它的最后一个对象被销毁。std::shared_ptr会调用delete,而我们的Lock类,需要的是解锁。4.转移底部资源的所有权:在极少数情况下,需要确保只有一个RAII对象管理资源,当复制RAII对象时,需要转移资源的所有权。1、禁止复制:如果允许复制RAII对象没有意义,这时应该禁止它。原创 2024-01-01 21:16:22 · 454 阅读 · 0 评论 -
条款13:以对象管理资源
为了防止资源泄漏,使用RAII对象,它们在构造函数中获取资源,并在析构函数中释放资源。两个常用的RAII类是shared_ptr和unique_ptr。shared_ptr通常是更好的选择,因为它在复制时的行为很直观。原创 2023-12-31 20:09:25 · 354 阅读 · 0 评论 -
条款 12:拷贝对象的所有部分
编译器生成的拷贝函数(拷贝构造函数,拷贝赋值运算符),会拷贝对象的所有数据,当你声明自己的拷贝函数时,就是在告诉编译器,默认实现中有你不喜欢的地方。如果两个拷贝函数有很多重复代码,可以先构造一个,再用另一个调用它吗?构造函数会初始化新对象,但赋值运算符只适用于已经初始化的对象。在调用赋值的时候data数据由于没有进行赋值拷贝,所以在子类的赋值中缺失了该数据。如果没有调用的话进行赋值的时候就会导致基类部分的数据没有赋值过去。出现这个问题的最狡猾的方式之一是通过继承。拷贝时,后期添加的成员被遗忘了。原创 2023-12-30 21:57:11 · 365 阅读 · 0 评论 -
条款 11:在 operator= 中处理“自我赋值“
当对象被赋值给自身时,确保operator=表现良好。技术途径包括:比较源对象和目标对象的地址、仔细的语句排序、copy-and-swap。确保在两个或多个对象相同(是同一个对象)的情况下,函数的行为仍然正确。原创 2023-12-24 23:21:58 · 338 阅读 · 0 评论 -
条款 10:令 operator= 返回一个指向 *this 的引用
实现这种操作的方式是,赋值操作返回一个指向左侧参数的引用。原创 2023-12-24 18:00:55 · 393 阅读 · 0 评论 -
条款 9:绝不在构造和析构过程中调用虚函数
解决这个问题有很多方法。一种是将logTransaction转换为非虚函数,然后要求派生类构造函数将必要的日志信息传递给基类构造函数。检测虚函数在构造或析构期间的调用并不总是那么容易。假设有个类的继承体系,用于建模股票交易,例如买入订单、卖出订单等。此类交易是可审计的,因此每次创建交易对象时,都需要在审计日志中创建适当的条目。原创 2023-12-24 17:44:20 · 378 阅读 · 0 评论 -
条款 8:别让异常逃离析构函数
析构函数永远不应该吐出(emit)异常。如果在析构函数中调用的函数可能抛出异常,则析构函数应该捕获任何异常,然后将其吞下或终止程序。如果类客户需要能够对操作期间抛出的异常做出反应,则类应该提供执行该操作的普通(即非析构函数)函数。原创 2023-12-23 22:31:24 · 377 阅读 · 0 评论 -
条款7:为多态基类声明虚析构函数
多态基类应该声明虚析构函数。如果一个类有虚函数,它应该有一个虚析构函数。非基类或非多态使用的类不应该声明虚析构函数。原创 2023-12-23 17:40:52 · 366 阅读 · 0 评论 -
条款6:若不想使用编译器自动生成的函数,就该明确拒绝
有些场景我们不需要编译器默认实现的构造函数,拷贝构造函数,赋值函数,这时候我们应该明确的告诉编译器,我们不需要,一个可行的方法是将拷贝构造函数和赋值函数声明为private。我们可以使用上面的类定义,编译器将阻止客户端拷贝HomeForSale对象的尝试,如果无意中试图在成员函数或友元函数中这样做,链接器将会报错。要禁用编译器自动提供的功能,请将相应的成员函数声明为private并且不提供任何实现。在c++11的标准中我们可以使用delete来删除函数,这样在编译的时候就会提示错误。原创 2023-12-20 22:56:05 · 358 阅读 · 0 评论 -
条款5:了解c++默默编写并调用了哪些函数
如果你不自己声明,编译器会替你声明(编译器版本的)拷贝构造函数、拷贝赋值运算符和析构函数。此外,如果你没有声明任何构造函数,编译器会为你声明一个默认构造函数。如果我们的类中存在以下情况:存在引用或者常量的情况,编译器可能拒绝为你的类生成相应的代码。我们可以看到报错了,这时候我们需要自己实现。原创 2023-12-17 23:03:50 · 1596 阅读 · 1 评论 -
条款4:确保对象在使用之前被初始化
手动初始化内置类型的对象,因为C++不确保会初始化它们。在构造函数中,优先使用成员初始化列表,而不是在构造函数体内赋值。以类中声明的顺序在初始化列表中列出数据成员。通过将非局部静态对象替换为局部静态对象,避免跨编译单元的初始化顺序问题。原创 2023-12-17 16:33:24 · 915 阅读 · 0 评论 -
条款3:尽量使用const
声明const可以借助编译器检测使用错误。const可以应用于任何作用域的对象、函数参数和返回类型,以及作为一个整体的成员函数。编译器强制执行位常量,但你应该使用逻辑常量进行编程。当const和非const成员函数具有本质上相同的实现时,可以通过让非const版本调用const版本来避免代码重复。原创 2023-12-16 23:14:25 · 386 阅读 · 0 评论 -
条款2:不要滥用宏
考虑到const、枚举和内联的可用性,对预处理器(特别是#define)的需求减少了,但并没有完全消除。#include仍然是必不可少的,#ifdef/#ifndef在控制编译方面继续发挥重要作用。对于简单常量,首选const对象或枚举,而不是#define。对于类似函数的宏,优先选择内联函数。原创 2023-12-03 16:13:04 · 864 阅读 · 0 评论