【Effective C++】构造/析构/赋值运算
条款05:了解C++默默编写并调用哪些函数
默认构造函数、拷贝构造函数、拷贝赋值函数、析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性。
当我们没有人为的定义上面的几个函数时,编译器会给我们构造默认的。
当成员变量里有const对象或引用类型时,编译器会不能合成默认的拷贝赋值函数;当一个基类把它的拷贝赋值函数定义为private时,它的派生类也不无生成默认的拷贝赋值函数,因为它无法完成基类成份的赋值。
条款06:若不想使用编译器自动生成的函数,就该明确拒绝
将拷贝构造函数和拷贝赋值操作符声明为private可以阻止类的拷贝和赋值行为,但是这样做的话,类的其他成员或友元依然可以访问,所以最终的解决方案是只声明不定义。
C++11中已经摒弃了上述的做法,而是通过删除函数来设定,只需要在对应的函数列表后加=delete即可。
我们可以为了阻止拷贝而设计一个基类,这个基类非常简单:
class Uncopyable
{
protected:
Uncopyable();
~Uncopyable();
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
为了阻止我们设计的其他类的拷贝动作,只需要继承上面这个类即可。
条款07:为多态基类声明virtual析构函数
当你需要手动的去delete一个指向基类的指针的时候,需要将该基类的析构函数设置为virtual,这样可以让delete时,动态的删除可能的派生类的成员。
不要无端的给一个没有virtual成员函数的类的析构函数声明为virtual,因为如果要实现virtual函数必须让类携带更多的信息(存储空间)。
标准的string不含任何virtual函数,所以不要把string定义为某个定义类的基类。类似的情况也发生成STL的其他容器如vector,list,set等上。
为一个抽像基类(不能定义对象实体)声明一个纯虚函数,而且要为这个纯虚函数要提供一份定义。
class AWOV{
public:
virtual ~AWOV() = 0; //声明纯虚析构函数
};
AWOV::~AWOV(){} // pure virtual析构函数的定义
并不是多所有基类设计都是为了多态(经base class接口处理derived class对象)用途,比如说在刚开始定义的Uncopyable类。
条款08:别让异常逃离析构函数
不要让析构函数吐出异常。如果一个被析构函数调用的函数可能抛出获异常,析构函数应该捕捉任何获异常,然后吞下它们(不传播)或结束程序。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。
条款09:绝不在构造和析构过程中调用virtual函数
假设你有个class继承体系,用来模拟股市交易如买进、卖出的订单等等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志中也需要创建一笔适当记录。
// 交易的基类
class Transaction
{
public:
Transaction();
virtual void logTransaction()const = 0;
//...
};
Transaction::Transaction()
{
// ...
logTransaction(); // 创建一笔交易的最后一步是日志里记录这笔交易
}
// 买入类
class BuyTransaction :public Transaction
{
public:
virtual void logTransaction()const; // 日志记录此型交易
//...
};
// 卖出类
class SellTransaction :public Transaction
{
public:
virtual void logTransaction()const; // 日志记录此型交易
};
现在如果我们有一笔买入的交易,我们定义了一个买入类的对象BuyTransaction b;
这个时候首先BuyTransaction类的构造函数被调用,但是首先Transaction构造函数一定会更早被调用,因为派生类的构造第一步是构造基类部分,当Transaction构造函数执行到调用logTransaction函数时,执行的是基类的版本,而并不是调用派生类里的logTransaction,虽然我们现在正在创建的是BuyTransaction。
Q:为何不能在基类的构造函数中调用virtual函数?
A:在base class构造函数执行时,derived class的成员变量尚未初始化,如果此时真的去访问派生类的virtual函数,那这个函数还没有被初始化,从而可能导致程序异常奔溃。
其实更根本的的原因是:在derived class对象的base class构造期间,对象的类型是base class而不是derived class。
类似的情况也发生在析构函数内,当BuyTransaction的析构函数执行基类的析构函数时,这时BuyTranscation类的成员将会被编译器忽略,base class执行的virtual函数都是基类里的版本。
这里如果我们还是想实现在创建日志的时候能够动态的创建不同类型的日志,我们可能通过向logTransaction中传入不同的参数来创建不同的日志,此时logTransaction将不再是一个viratual 函数。
条款10:令operator=返回一个reference* this
x=y=z=15实际上是采用右赋值的,这个连锁赋值可以解析为:x=(y=(z=15))。这里15先被赋值给z,然后其结果(更新后的z)再被赋值给y,然后其结果(更新后的y)再被赋值给x。
所以如果我们想保证y和z在完成赋值后能够对自己更新,则需要operator=返回一个本身的引用。
条款11:在operator=中处理"自我赋值"
我们定义了一个Widget的类,这个类中保存一个从heap中分配得到的指针
class Bitmap;
class Widget{
public:
Widget& operator=(const Widget& rhs);
private:
Bitmap* pb;
};
Widget& Widget::operator=(const Widget& rhs)
{
delete pb; // 释放当前类里的指针
pb = new Bitmap(*rhs.pb); // 拷贝一个新指针
return *this;
}
上面的代码看起来合理,实际上一旦出现自我赋值的时候就很不安全,因为它首先释放掉了内存空间,再从本身内存地址拷贝对象的时候,该地址所指向的对象已经不存在了。
我们可以像下面这样写出异常安全的代码
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb; // 保存原来的pb
//令pb指向*pb的一个副本,即使new Bitmap异常也没问题
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
条款12:复制对象时勿忘其每一个成分
当你编写一个拷贝构造函数或拷贝赋值运算符时,请确保有的局部成员变量都被拷贝以及调用所有的基类的适当的拷贝函数。
虽然拷贝赋值操作符与拷贝构造函数做了类似的事情,但是你不能在拷贝赋值操作里调用拷贝构造函数,因为你试图对一个已经定义的对象从重定义并初始化。
如果你发现你的copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数来给两者调用。
【Effective C++】构造/析构/赋值运算的更多相关文章
- Effective C++ —— 构造/析构/赋值运算(二)
条款05 : 了解C++默默编写并调用哪些函数 编译器可以暗自为class创建default构造函数.copy构造函数.copy assignment操作符,以及析构函数. 1. default构造函 ...
- Effective C++ -- 构造析构赋值运算
05.了解C++默默编写并调用哪些函数 编译产生的析构函数时non-virtual,除非这个类的基类析构函数为virtual 成员变量中有引用和const成员时,无法自己主动生成copy assign ...
- 《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- 《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- EffectiveC++ 第2章 构造/析构/赋值运算
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 2 构造 / 析构 / 赋值 条款 05:了解C++ ...
- Effective C++ 笔记二 构造/析构/赋值运算
条款05:了解C++默默编写并调用哪些函数 编译器默认声明一个default构造函数.一个copy构造函数.一个copy assignment操作符和一个析构函数.这些函数都是public且inlin ...
- Effective C++笔记:构造/析构/赋值运算
条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函 ...
- Effective C++笔记(二):构造/析构/赋值运算
参考:http://www.cnblogs.com/ronny/p/3740926.html 条款05:了解C++默默编写并调用哪些函数 如果自定义一个空类的话,会自动生成默认构造函数.拷贝构造函数. ...
- Effective C++ 2.构造 析构 赋值运算
//条款07:为多态基类声明virtual析构函数 // 1.若基类的析构函数不定义为虚函数,由于基类的指针或引用可以指向派生类的对象,则在删除基类对象的时候可能会出错,导致破坏数据结构. // 2. ...
随机推荐
- AGC006
AtCoder Grand Contest 006 <br > 心血来潮,开了一套AGC..... 然后发现各种不会做.........感觉智商被AGC摁在地上摩擦...... <b ...
- SpringMVC整合MongoDB
首先,在pom文件中新增spring-data-mongodb的依赖: <dependency> <groupId>org.springframework.data</g ...
- Interface Builder中的技巧
在我工作中经常会遇到有人吐槽Xcode中的interface builder(以下简称IB)不好用的开发者.在我看来,IB是一个非常棒的可视化开发工具,可以非常快捷的设置UI控件的大部分常用属性.下面 ...
- 【Protocol Buffers】grpc默认使用的Google 开源的一套成熟的结构数据序列化机制
grpc默认使用的Google 开源的一套成熟的结构数据序列化机制 参考地址:https://blog.csdn.net/shensky711/article/details/69696392 参考地 ...
- 采用scp命令在Linux系统之间copy文件
不同的Linux之间copy文件常用有3种方法,第一种就是ftp,也就是其中一台Linux安装ftp Server,这样可以另外一台使用ftp的client程序来进行文件的copy.第二种方法就是采用 ...
- inputclean插件的使用方法
inputclean插件的使用方法 语言:javascript 是jquery插件, 目的:给文本框添加×,点击×,就可以清空文本框内容. 如图: 详细交互效果: 1,当文本框聚焦时,×永久显示,无论 ...
- Spring.net1.3.1+Nhibernate3.0+Mysql/Access/SqlServer/Oracel/SQlite
详情请看我的博文:http://www.ruisoftcn.com/blog/article.asp?id=999
- Python中cv2库和matplotlib库色彩空间排布不一致
今天在python中读如图片时发现以下问题: 1.在from matplotlib import pyplot as plt之后,再import cv2 cv2.imshow()不能正常使用,还不知道 ...
- 抽象类的子类能够new
纠结了半天,我以为继承了Activity后不能new这里被那个onCreate方法迷惑了以为会出现故障一直没直接创建对象类使用 后来试了试才知道 activity似乎是一个抽象类吧. 你要用他的方法, ...
- cocos2dx-3.1 接入多盟广告sdk+Android (2)
上篇,把基本工作做完了,接下来,在C++下加入代码. 1.打开win32下的project,加入一个类.DomobAd.代码例如以下, <pre name="code" cl ...