
More Effective C++
vvc223c
这个作者很懒,什么都没留下…
展开
专栏收录文章
- 默认排序
- 最新发布
- 最早发布
- 最多阅读
- 最少阅读
-
More Effective C++ 27:要求或禁止在堆中产生对象
堆中建立对象 为了执行这种限制,你必须找到一种方法禁止以调用new以外的其它手段建立对象。这很容易做到。非堆对象在定义它的地方被自动构造,在生存时间结束时自动被释放,所以只要禁止使用隐式的构造函数和析构函数,就可以实现这种限制。 把这些调用变得不合法的一种最直接的方法是把构造函数和析构函数声明为 private 这样做副作用太大。没有理由让这两个函数都是 private。最好让析构函数成为 private,让构造函数成为 public。 例如,如果我们想仅仅在堆中建立代表无限精确度数字的对象,可以这样做:原创 2020-07-16 22:14:13 · 424 阅读 · 0 评论 -
More Effective C++ 26:限制某个类所能产生的对象数量
例如你在系统中只有一台打印机,所以你想用某种方式把打印机对象数目限定为一个。或者你仅仅取得 16 个可分发出去的文件描述符,所以应该确保文件描述符对象存在的数目不能超过 16 个。你如何能够做到这些呢?如何去限制对象的数量呢? 每次实例化一个对象时,我们很确切地知道一件事情:“将调用一个构造函数。”事实确实这样,阻止建立某个类的对象,最容易的方法就是把该类的构造函数声明在类的 private域或者声明为delete: class CantBeInstantiated { private: CantB原创 2020-07-15 18:33:17 · 485 阅读 · 0 评论 -
More Effective C++ 25:将构造函数和非成员函数虚拟化
假设你编写一个程序,用来进行新闻报道的工作,每一条新闻报道都由文字 或图片组成。你可以这样管理它们: class NLComponent //用于 newsletter components的抽象基类 { public: ... //包含至少一个纯虚函数 }; class TextBlock: public NLComponent { public: ... // 不包含纯虚函数 }; class Graphic: public NLComponent { public:原创 2020-07-15 15:59:34 · 298 阅读 · 0 评论 -
More Effective C++ 24:理解虚拟函数、多继承、虚基类和 RTTI 所需的代价
当调用一个虚拟函数时,被执行的代码必须与调用函数的对象的动态类型相一致 编译器如何能够高效地提供这种行为呢?大多数编译器是使用 virtual table 和 virtual table pointers。virtual table 和 virtual table pointers 通常被分别地称为 vtbl 和 vptr。 一个 vtbl 通常是一个函数指针数组。在程序中的每个类只要声明了虚函数或继承了虚函数,它就有自己的 vtbl,并且类中 vtbl的项目是指向虚函数实现体的指针。例如,如下这个类定义:原创 2020-07-15 15:18:35 · 307 阅读 · 0 评论 -
More Effective C++ 23:考虑变更程序库
考虑 iostream 和 stdio 程序库,对于 C++程序员来说两者都是可以使用的。 iostream程序库与C中的stdio相比有几个优点。例如它是类型安全的,它是可扩展的。然而在效率方面,iostream程序库总是不如stdio,因为stdio产生的执行文件与 iostream 产生的执行文件相比尺寸小而且执行速度快。 应该注意到 stdio 的高效性主要是由其代码实现决定的。iostream 和 stdio 之间性能的对比不过是一个例子,这并不重要,重要的是具有相同功能的不同的程序库在性能上采取原创 2020-06-21 15:52:03 · 359 阅读 · 0 评论 -
More Effective C++ 22:考虑用运算符的赋值形式取代其单独形式
大多数程序员认为如果他们能这样写代码: x = x + y; x = x - y; 那他们也能这样写: x += y; x -= y; 但是,如果 x 和 y 是用户定义的类型,就不能确保这样。 operator+、operator=和operator+=之间没有任何关系,因此如果你想让这三个 operator同时存在并具有你所期望的关系,就必须自己实现它们。 同理,operator -, *, /, 等等也一样。 确保 operator 的赋值形式(比如operator+=)与一个operator的单原创 2020-06-21 15:43:24 · 262 阅读 · 0 评论 -
More Effective C++ 21:通过重载避免隐式类型转换
考虑以下类: class UPInt { public: UPInt(); UPInt(int value); ... }; const UPInt operator+(const UPInt& lhs, const UPInt& rhs); UPInt upi1, upi2; ... UPInt upi3 = upi1 + upi2; 现在考虑下面这些语句: upi3 = upi1 + 10; upi3 = 10 + upi2; 这些语句也能够成功运行。方法是通过建原创 2020-06-21 15:08:10 · 351 阅读 · 0 评论 -
More Effective C++ 20:协助完成返回值优化
一个返回对象的函数很难有较高的效率,因为传值返回会导致调用对象内的构造和析构函数,这种调用是不能避免的。问题很简单:一个函数要么为了保证正确的行为而返回对象要么就不这么做。如果它返回了对象,就没有办法摆脱被返回的对象。 考虑以下类: class Rational { public: Rational(int numerator = 0, int denominator = 1); ... int numerator() const; int denominator() const; }原创 2020-06-15 18:22:31 · 295 阅读 · 0 评论 -
More Effective C++ 19:理解临时对象的来源
C++中真正的临时对象是看不见的,它们不出现在你的源代码中。 建立一个没有命名的非堆对象会产生临时对象。这种未命名的对象通常在两种条件下产生: 为了使函数成功调用而进行隐式类型转换和函数返回对象时. 首先考虑为使函数成功调用而建立临时对象这种情况: 当传送给函数的对象类型与参数类型不匹配时会产生这种情况。 比如: 一个函数,它用来计算一个字符在字符串中出现的次数 size_t countChar(const string& str, char ch); char buffer[MAX_STRING_原创 2020-06-15 17:55:57 · 269 阅读 · 0 评论 -
More Effective C++ 18:分期摊还期望的计算
这个条款的核心是over-eager evaluation(过度热情计算法):在要求你做某些事情以前就完成它们. 考虑下面的类,用来表示放有大量数字型数据的一个集合: template<class NumericalType> class DataCollection { public: NumericalType min() const; NumericalType max() const; NumericalType avg() const; ... }; 假设 m原创 2020-06-15 15:15:51 · 287 阅读 · 0 评论 -
More Effective C++ 17:考虑使用懒惰计算法
从效率的观点来看,最佳的计算就是根本不计算。 懒惰计算法广泛适用于各种应用领域,从四个部分讲述 引用计数 class String { ... }; String s1 = "Hello"; String s2 = s1; // 调用 string 复制构造函数 通常 string 拷贝构造函数让 s2 被 s1 初始化后,s1 和 s2 都有自己的”Hello”拷贝。这种拷贝构造函数会引起较大的开销,因为要制作 s1 值的拷贝,并把值赋给 s2,这通常需要用 new 操作符分配堆内存。 需要调用 str原创 2020-06-14 15:52:40 · 338 阅读 · 0 评论 -
More Effective C++ 16:80-20准则
80-20 准则 大约 20%的代码使用了 80%的程序资源;大约 20%的代码耗用了大约 80%的运行时间;大约 20%的代码使用了 80%的内存;大约 20%的代码执行 80%的磁盘访问;80%的维护投入于大约 20%的代码上。 基本的观点是:软件整体的性能取决于代码组成中的一小部分。 面对运行速度缓慢或占用过多内存的程序,你该如何做呢? 80-20 准则的含义是:胡乱地提高一部分程序的效率不可能有很大帮助。正确的方法是用 profiler 程序识别出令人讨厌的程序的 20%部分。 用尽可能多的数据 p原创 2020-06-14 11:56:21 · 202 阅读 · 0 评论 -
More Effective C++ 15:了解异常处理的系统开销
为了在运行时处理异常,程序要记录大量的信息。 无论执行到什么地方,程序都必须能够识别出如果在此处抛出异常的话,将要被释放哪一个对象;对于每一个 try 块,他们都必须跟踪与其相关的 catch 子句以及这些catch子句能够捕获的异常类型 不使用任何异常处理特性也要付出代价 你需要空间建立数据结构来跟踪对象是否被完全构造,你也需要 CPU 时间保持这些数据结构不断更新。这些开销一般不是很大,但是采用不支持异常的方法编译的程序一般比支持异常的程序运行速度更快所占空间也更小。 理论上,你不能对这些代价进行选择:原创 2020-06-14 11:49:07 · 253 阅读 · 0 评论 -
More Effective C++ 14:审慎使用异常规格
函数unexpected 默认的行为是调用函数 terminate,而 terminate 默认的行为是调用函数 abort,所以一个违反异常规格的程序其默认的行为就是停止运行。在激活的栈中的局部变量没有被释放,因为abort在关闭程序时不进行这样的清除操作。对异常规格的触犯变成了一场并不应该发生的灾难。 … … 不幸的是,我们很容易就能够编写出导致发生这种灾难的函数。编译器仅仅部分地检测异常的使用是否与异常规格保持一致。一个函数调用了另一个函数,并且后者可能抛出一个违反前者异常规格的异常。 比如:A函数调原创 2020-06-08 13:03:34 · 211 阅读 · 0 评论 -
More Effective C++ 13:通过引用捕获异常
当你写一个 catch 子句时,必须确定让异常通过何种方式传递到 catch 子句里。你可以有三个选择: 1.指针2.传值3.引用 通过指针 通过指针方式捕获异常在理论上这种方法的实现对于这个过程来说是效率最高的。因为在传递异常信息时,只有采用通过指针抛出异常的方法才能够做到不拷贝对象。比如: class exception { ... }; // 来自标准 C++库(STL) // 中的异常类层次 // (参见条款 12) void someFunction() { static exce原创 2020-06-08 10:27:15 · 463 阅读 · 0 评论 -
More Effective C++ 12:理解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”间的差异
从语法上看,在函数里声明参数与在 catch 子句中声明参数几乎没有什么差别: class Widget { ... }; //一个类 void f1(Widget w); // 一些函数,其参数分别为 void f2(Widget& w); // Widget, Widget&,或 void f3(const Widget& w); // Widget* 类型 void f4(Widget *pw); void f5(const Widget *pw); catch (原创 2020-05-24 16:12:41 · 225 阅读 · 0 评论 -
More Effective C++ 11:禁止异常信息传递到析构函数外
栈展开(stack-unwinding) 抛出异常时,将暂停当前函数的执行,开始查找匹配的catch子句。首先检查throw本身是否在try块内部,如果是,检查与该try相关的catch子句,看是否可以处理该异常。如果不能处理,就退出当前函数,并且释放当前函数的内存并销毁局部对象,继续到上层的调用函数中查找,直到找到一个可以处理该异常的catch。这个过程称为栈展开。 有两种情况下会调用析构函数: 1.在正常情况下删除一个对象,例如对象超出了作用域或被显式地delete 2.异常传递的堆栈辗转开解(stac原创 2020-05-24 14:38:53 · 293 阅读 · 0 评论 -
More Effective C++ 10:在构造函数中防止资源泄漏
如果你正在开发一个具有多媒体功能的通讯录程序。这个通讯录除了能存储通常的文字信息如姓名、地址、电话号码外,还能存储照片和声音。 可以这样设计: class Image // 用于图像数据 { public: Image(const string& imageDataFileName); ... }; class AudioClip // 用于声音数据 { public: AudioClip(const string& audioDataFileName); .原创 2020-05-24 11:25:45 · 292 阅读 · 0 评论 -
More Effective C++ 09:使用析构函数防止资源泄漏
假设,你正在为一个小动物收容所编写软件,小动物收容所是一个帮助小狗小猫寻找主人的组织。 … … 每天收容所建立一个文件,包含当天它所管理的收容动物的资料信息, 你的工作是写一个程序读出这些文件然后对每个收容动物进行适当的处理. … … 完成这个程序一个合理的方法是定义一个抽象类然后为小狗和小猫建立派生类。一个虚拟函数 processAdoption 分别对各个种类的动物进行处理: class ALA { public: virtual void processAdoption() = 0; ..原创 2020-05-15 14:59:32 · 231 阅读 · 0 评论 -
More Effective C++ 08:理解各种不同含义的 new 和 delete
string *ps = new string("Memory Management"); 当你写这样的代码,你使用的new是new操作符。 这个操作符就像sizeof一样是语言内置的,你不能改变它的含义,它的功能总是一样的。它要完成的功能分成两部分。第一部分是分配足够的内存以便容纳所需类型的对象。第二部分是它调用构造函数初始化内存中的对象. 你所能改变的是如何为对象分配内存。new 操作符调用一个函数来完成必需的内存分配,你能够重写或重载这个函数来改变它的行为。new操作符为分配内存所调用函数的名字是o原创 2020-05-15 14:00:02 · 195 阅读 · 0 评论 -
More Effective C++ 07:不要重载&&,||, 或,
与 C 一样,C++使用布尔表达式短路求值法. 比如: char *p; ... if ((p != 0) && (strlen(p) > 10)) 这里不用担心当 p为空时 strlen 无法正确运行,因为如果 p 不等于 0 的测试失败,strlen不会被调用。 同样: int rangeCheck(int index) { if ((index < lowerBound) || (index > upperBound)) ... ... } 如果 i原创 2020-05-14 16:48:28 · 250 阅读 · 0 评论 -
More Effective C++ 06:自增、自减操作符前缀形式与后缀形式的区别
重载函数间的区别决定于它们的参数类型上的差异,但是不 论是自增或自减的前缀还是后缀都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个 int 类型参数,当函数被调用时,编译器传递一个 0 做为 int 参数的值给该函数: class UPInt { public: UPInt& operator++(); // ++ 前缀 const UPInt operator++(int); // ++ 后缀 UPInt& operator--(); // -- 前缀 c原创 2020-05-14 16:19:48 · 264 阅读 · 0 评论 -
More Effective C++ 05:谨慎定义类型转换函数
C++编译器允许把 char 隐式转换为 int 和从 short 隐式转换为 double。因此当你把一个 short 值传递给准备接受 double 参数值的函数时,依然可以成功运行。 你对这些类型转换是无能为力的,因为它们是语言本身的特性。不过当你增加自己的类型时,你就可以有更多的控制力,因为你能选择是否提供函数让编译器进行隐式类型转换。 有两种函数允许编译器进行这些的转换:单参数构造函数和隐式类型转换运算符。单参数构造函数是指只用一个参数即可以调用的构造函数。该函数可以是只定义了一个参数,也可以是虽原创 2020-05-14 15:42:26 · 247 阅读 · 0 评论 -
More Effective C++ 04:避免无用的默认构造函数
在很多时候,对于很多对象来说,不利用外部数据进行完全的初始化是不合理的。比如一个没有输入姓名的地址簿对象,就没有任何意义。 在一些公司里, 所有的设备都必须标有一个公司ID号码,所以在建立对象以模型化一个设备时,不提供一个合适的ID号码,所建立的对象就根本没有意义。 考虑以下的类: class EquipmentPiece { public: EquipmentPiece(int IDNumber); ... }; 因为EquipmentPiece 类没有一个默认构造函数,所以在三种情况下使用原创 2020-05-14 15:06:51 · 312 阅读 · 0 评论 -
More Effective C++ 03:不要对数组使用多态
假设你有一个类 BST和继承自 BST 类的派生BalancedBST: class BST { ... }; class BalancedBST: public BST { ... }; BST 和 BalancedBST 只包含 int 类型数据。 有这样一个函数,它能打印出 BST 类数组中每一个 BST 对象的内容: void printBSTArray(ostream& s, const BST array[], int numElements) { for (int i =原创 2020-05-14 13:56:10 · 213 阅读 · 0 评论 -
More Effective C++ 02:尽量使用C++风格类型的转换
相比于C++风格类型的转换,C风格的类型转换太过粗暴,允许你在任何类型之间进行转换。 1.例如把一个指向 const 对象的指针转换成指向非 const 对象的 指针(即一个仅仅去除 const 的类型转换),把一个指向基类的指针转换成指向子类的指针(即完全改变对象类型)。传统的C风格的类型转换不对上述两种转换进行区分。 2.C风格的类型转换在程序语句中难以识别。在语法上,类型转换由圆括号和标识符组成,而这些可以用在C++中的任何地方。人工阅读很可能忽略了类型转换的语句。 C++通过引进四个新的类型转换操作原创 2020-05-14 13:22:24 · 287 阅读 · 0 评论 -
More Effective C++ 01:指针与引用的区别
指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢? 在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。 因为引用肯定会指向一个对象,在 C++里,引用应被初始化。 指针则没有这样的限制 不存在指向空值的引用这个事实意味原创 2020-05-14 11:26:14 · 209 阅读 · 0 评论