第二章的内容比较简单,基本没有太多深意,从其标题就能想到其内容的那种,所以就只稍微做一下总结吧。
Item 5: 了解C++默认编写并调用哪些函数
编译器暗自为class创建的函数(如果用户没有自己声明)包括:default构造函数,copy构造函数,copy assignment操作符以及析构函数。
自动生成的default构造函数、析构函数做的事情包括调用base classes和non-static成员变量的构造函数和析构函数。编译器生产的析构函数是个non-virtual函数,除非这个class的base class自身声明有virtual析构函数。
至于copy构造函数和copy assignment操作符,编译器创建的版本只是单纯地将来源对象的每一个non-static成员变量拷贝到目标对象。但是有一个问题就是,对于包含有reference成员或const成员的class,编译器不知道如何在它自己生成的赋值函数内面对它们,所以如果你希望你的class支持copy构造或赋值操作,对于这样的class,你要自己实现它。另外,如果某个base class将copy assignment操作符声明为private,编译器将拒绝为其derived class生成一个copy assignment操作符。
Item 6: 如果不想使用编译器自动生成的函数,就该明确拒绝
一旦你定义了自己的构造函数,不论函数参数如何,编译器都不再自动帮你产生构造函数。
如果你想阻止copying,则要声明一个private的copy构造函数和copy assignment操作符,并且不去定义它们。这样,一旦有人尝试进行copy构造或赋值,编译器就会报错。你也可以继承如下一个不可复制的父类:
class Uncopyable{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable( const Uncopyable& );
Uncopyable& operator=(const Uncopyable& );
};
为阻止你的类如HomeForSale对象被拷贝,可以让它继承Uncopyable:
class HomeForSale: private Uncopyable{
...
};
Item 7: 为多态基类声明virtual析构函数
任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。
如果class不含virtual函数,通常表示它并不意图被用做一个base class。当class不企图被当作base class,令其析构函数为virtual往往反而浪费了内存,因为,欲实现virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数被调用。这份信息通常是由一个所谓vptr(virtual table pointer)指针指出。vptr指向一个由函数指针构成的数组,称为vtbl(virtual table):每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl——编译器在其中寻找适当的函数指针。
由virtual析构函数的问题还有一个启发,那就是,不要企图继承一个标准容器或任何其他带有non-virtual析构函数的class。原因跟base class一定要带有virtual析构函数是一样的,如果我们通过基类的指针释放一个继承类,将会调用错误的析构函数!
Item 8: 别让异常逃离析构函数
1. 对于C++,有两个异常同时存在的情况下,程序若不是结束执行,就是导致不明确行为。因此假如某对象的析构函数可能抛出异常,当我们试图释放一个包含装有该类对象的容器时,假设在析构第一个元素期间,有个异常被抛出,容器中的其他元素还是要被销毁,因此将调用它们各个析构函数,如果在这期间又有另一元素在析构过程中抛出了异常,故事将如何发展便不得而知了。
2. 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
3. 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
Item 9: 绝不在构造和析构过程中调用virtual函数,因为这类调用从不下降至derived class。
Item 10: 令operator=返回一个reference to *this
这是为了实现类似如下的连锁形式:
int x, y, z;
x = y = z = 10;
Item 11: 在operator=中处理“自我赋值”
凡是有指针、下标、reference的赋值,就有潜在自我赋值的可能性。自我赋值可能造成“在停止使用资源之前释放了它”的危险。例如下面这个例子:
class Bitmap{ ... }
class Widget{
...
private:
Bitmap* pb;
};
//下面是operator=的实现代码:
Widget&
Widget::operator=(const Widget& rhs ){
delete pb;
pb = new Bitmap( *rhs.pb );
return *this;
}
*this和rhs有可能是同一个对象,果真如此delete就不只是销毁当前对象的bitmap,它也销毁rhs的bitmap!
有三种方法处理“自我赋值”的问题:
(1)identity test
Widget& Widget::operator( const Widget& rhs ){
if( this == &rhs ) return *this;
delete pb;
pb = new Bitmap( *rhs.pb );
return *this;
}
这样的方式还是可能带来异常(分配时内存不足,或者是Bitmap的copy构造函数抛出异常),Widget最终会有一个指针指向一块被删除的Bitmap,而且我们无法安全地删除它。
(2)调整语句顺序
Widget& Widget::operator( const Widget& rhs ){
Bitmap* pOrig = pb;
pb = new Bitmap( *rhs.pb );
delete pOrig;
return *this;
}
现在,如果new Bitmap抛出异常,pb保持原状。
(3)copy and swap技术
class Widget{
...
void swap( Widget& rhs ); //交换*this和rhs的数据
...
};
Widget& Widget::operator=( const Widget& rhs ){
Widget temp( rhs ); //为rhs数据制作一份复件
swap( temp ); //将*this数据和上述复件的数据交换
return *this;
}
Item 12: 复制对象时勿忘其每一个成分
当你编写一个copying函数,请确保(1)复制所有local成员变量,(2)调用所有base class内的适当的copying构造函数,(3)不要尝试以某个copying函数实现另外一个copying函数。
分享到:
相关推荐
我们在前面文章已经介绍了,如果类没有手动声明拷贝构造函数或拷贝赋值运算符,那么编译器会为我们自动生成这两个成员,并且它们的行为是:将被拷贝的对象的所有成员做一份拷贝 二、复制对象时确保复制所有成员 下面...
在前面一篇文章中(https://blog.csdn.net/qq_41453285/article/details/104165762),我们介绍了C++编译器会为我们的class生成默认的构造函数、析构函数、拷贝构造函数、拷贝赋值运算符 但是有些情况下,我们不...
在C++编程中,理解和掌握构造函数、析构函数、拷贝构造函数和赋值运算符的使用...以上就是《Effective C++》中关于构造析构赋值运算的部分核心概念,理解并遵循这些最佳实践将有助于编写更健壮、更易于维护的C++代码。
2.构造/析构/赋值运算 条款05:了解C++默默编写并调用哪些函数 条款06:若不想使用编译器自动成生的函数,就该明确拒绝 条款07:为多态基类声明Virtual析构函数 条款08:别让异常逃离析构函数 条款09:绝不在...
- **语法简洁**:Go语言的语法设计简洁明了,避免了C++或Java中的复杂特性,如指针运算、模板等,使得代码更易读、易写。 - **结构体与方法**:Go语言中,通过定义结构体来创建自定义类型,并可以为结构体添加方法...
#### 构造/析构/赋值运算(第二章) 这一章节深入探讨了C++中的构造、析构和赋值运算符,包括: - **条款05至06**:理解编译器自动生成的构造、析构和赋值运算符,以及何时应该显式定义它们。 - **条款07**:强调...
在C++中,对象的构造、析构和赋值运算具有重要的作用。例如,了解编译器如何自动生成拷贝构造函数、赋值操作符和析构函数,以及何时需要显式地拒绝这些自动生成的函数。 三、资源管理 资源管理是C++编程中的一个...
2. 构造、析构和赋值运算。在这一部分中,Scott Meyers探讨了对象生命周期的管理问题,包括构造函数、析构函数和赋值运算符的正确使用方法。 3. 资源管理。这部分是本书的核心之一,它详细讲解了如何通过RAII...
### More_Effective_C++(WQ版).pdf #### 知识点概览: 1. **C++基础知识:** - 指针与引用的区别 - 使用C++风格的类型转换 - 不要对数组使用多态 - 避免无用的缺省构造函数 2. **运算符:** - 谨慎定义类型...
掌握析构函数在对象生命周期结束时清理资源的重要性,以及了解移动构造函数和移动赋值运算符在C++11中如何提高性能。 3. **常量与引用**:正确使用常量能确保对象在程序执行期间不被意外修改,而引用提供了一种安全...
2. 构造/析构/赋值运算 constructors, destructors, and assignment operators 条款05:了解c++ 默默编写并调用哪些函数 know what functions c++ silently writes and calls. 条款06:若不想使用编译器自动生成的...
《More Effective C++》是C++编程领域的一本经典书籍,由Scott Meyers撰写,旨在帮助程序员提升C++编程的效率和质量。书中的每个ITEM都聚焦于一个具体的编程实践,通过深入讲解和实例分析,让读者理解并掌握C++中的...
《More Effective C++》是C++编程领域的一本经典书籍,由Scott Meyers撰写,旨在帮助程序员避免常见的C++编程陷阱,提升代码质量和效率。书中的每个"ITEM"都聚焦于一个具体的C++编程技巧或者最佳实践。 1. **指针与...
4. **构造/析构/赋值运算** - **了解C++默默编写并调用哪些函数**: C++会自动生成默认构造函数、拷贝构造函数、赋值运算符和析构函数,理解这些函数的作用是必要的。 - **明确拒绝编译器自动生成的函数**: 当不...
### More+Effective+C++(中文完全版) #### 译序与导读 本书《More Effective C++》由Scott Meyers撰写,侯捷翻译并撰写了导读部分。本书旨在帮助读者更深入地理解和掌握C++编程语言的高级用法,提高代码质量和...
Scott Meyers所著的《More Effective C++》是一本被广泛推崇的经典著作,旨在帮助程序员掌握更高效、更可靠的C++编程技巧。本书不仅包含了作者多年的经验总结,还深入探讨了许多在实际开发中容易忽视但又极其重要的...