如果程序员没有定义,那么编译器会默认隐式为你创建一个copy构造函数,一个copy赋值操作符,一个析构函数。另外如果你没有声明任何构造函数,编译器会为你声明一个default构造函数。

但是只有当这些函数被用到时,他们才会被创建。例如Empty a(b),会创建copy构造函数。

[cpp] view plain copy
  1. class Empty
  2. {
  3. Empty(){...}//default构造函数,如果没有声明任何构造函数,编译器会自动声明
  4. Empty(const Empty& rhs){...}//copy构造函数,如果没有声明,编译器自动声明
  5. ~Empty(){}//析构函数,同copy构造函数
  6. Empty& operator=(const Empty& rhs){...}//copy复制操作符,同上
  7. };

对于copy赋值操作符,有一个需要注意的地方。考虑这样一个类NamedObject,包含一个string& nameValue和一个const int objectValue 。假设你使用编译器为你创建的copy赋值操作符,看看会发生什么:

[cpp] view plain copy
  1. string newDog("Persephone");
  2. string oldDog("Satch");
  3. NamedObject p(newDog,2);
  4. NamedObject s(oldDog,36);
  5. p=s;//将s拷贝赋值到p

p的reference对象nameValue被重新赋值了!我们知道reference对象是不能变动的,因此这总是错误的。同样p的const int objectValue也被改变。

因此如果想在一个内含reference或const成员的类内支持赋值操作,你必须定义自己的copy赋值操作符。

如果你不使用copy构造函数或者copy赋值操作符,你应该将他们声明为private并且不定义它。这样可以防止友元函数或成员函数或派生类使用它。

为多态基类声明virtual析构函数

为防止派生类在销毁时发生错误,即在delete派生类时只有基类部分被销毁,属于派生类的地方却还在,你应该将基类的析构函数声明为virtual。

同时,无端将所有类的析构函数声明为virtual是个错误的习惯,因为virtual会增加对象体积,造成不必要的资源浪费。一个心得是,在类内至少包含一个virtual函数时才为他声明virtual析构函数。

有时候你希望拥有一个抽象类,但是却没有纯虚函数,你可以为它声明一个纯虚析构函数,这样那个类就成为了抽象类。

绝不在构造和析构过程中调用virtual函数

当一个派生类调用构造函数时,若其调用了virtual函数,virtual函数调用的版本为其基类的版本,这不是我们希望看到的。这是因为在派生类对象的基类构造期间,对象的类型是基类而不是派生类。不只virtual函数会被编译器解析至基类,若使用运行期类型信息(dynamic_cast和typeid),也会把对象视为基类。

但是如果你想确保每次一个(继承体系上的)类的对象被创建,就会有适当版本的对象函数(多态函数)被调用,也就是在初始化的过程中调用。你可以将该函数声明为非virtual的,然后每次给他传递必要信息。即如下形式:

[cpp] view plain copy
  1. class Transaction
  2. {
  3. public:
  4. explicit Transaction(constant string& logInfo)
  5. {
  6. logTransaction(logInfo);//调用logTransaction
  7. }
  8. void logTransaction(logInfo) const;//声明为非virtual
  9. };
  10. class BuyTransaction:public Transaction
  11. {
  12. public:
  13. BuyTransaction(parameters):Transaction(parameters){}//每个BuyTransaction都有专属自己的logTransaction信息
  14. };

关于拷贝赋值操作符,有两个编程好习惯

  • 令operator=返回一个reference to *this
  • 在operator=中处理“自我赋值”
[cpp] view plain copy
  1. int x,y,z;
  2. x=y=z=1;

上面的代码能通过编译并且表现得完全符合我们的预期。为了使我们的代码和C++规范保持一致,我们也应该令operator=返回一个对*this的引用。当然你不这样做编译器也不会报错,只是这是一个共同遵守的约定。

某些情况下,会出现一种令我们意想不到的情况:对象自我赋值,如果你够聪明,你当然不会写出这样的代码,但是你不能保证使用你的代码的人也像你一样聪明,所以,这种情况应该被我们考虑到。

看看一个不安全的operator=的实现,看看会发生什么:

[cpp] view plain copy
  1. Widget& Widget::operator=(cosnt Widget& rhs)//widget类中包含了一个Bitmap类的成员pb
  2. {
  3. delete pb;
  4. pb= new Bitmap(*rhs.pb);
  5. return *this;
  6. }

上面的代码一般人当然会这么实现,但是看看当被自我赋值的时候会发生什么。它先删除了自己的pb对象,然后又将pb赋值给它自己,但是我们知道它已经被删除了,这是很明显的一个错误。

一个很好的解决方法是,以下的实现方式:

[cpp] view plain copy
  1. Widget& Widget::operator(cosnt Widget& rhs)
  2. {
  3. Bitmap* pOrig = pb;
  4. pb = new Bitmap(*ths.pb);
  5. delete pOrig;
  6. return *this;
  7. }

拷贝函数应该确保复制对象内的所有成员变量及所有基类成分,因此你应该为派生类撰写拷贝构造函数,因为如果不这样做,派生类的default构造函数会对基类进行default构造,也就是说,派生类的基类部分没有被拷贝,这显然是不符合我们期望的。所以,你应该为派生类撰写拷贝函数。

《Effective C++》读书笔记 被你忽略的关于构造析构赋值的更多相关文章

  1. Effective STL 读书笔记

    Effective STL 读书笔记 标签(空格分隔): 未分类 慎重选择容器类型 标准STL序列容器: vector.string.deque和list(双向列表). 标准STL管理容器: set. ...

  2. Effective STL读书笔记

    Effective STL 读书笔记 本篇文字用于总结在阅读<Effective STL>时的笔记心得,只记录书上描写的,但自己尚未熟练掌握的知识点,不记录通用.常识类的知识点. STL按 ...

  3. 《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

  4. 《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

  5. Effective C++读书笔记(转)

    第一部分 让自己习惯C++ 条款01:视C++为一个语言联邦 一.要点 ■ c++高效编程守则视状况而变化,取决于你使用c++的哪一部分. 二.扩展 将c++视为一个由相关语言组成的联邦而非单一语言会 ...

  6. effective c++读书笔记(一)

    很早之前就听过这本书,找工作之前读一读.看了几页,个人感觉实在是生涩难懂,非常不符合中国人的思维方式.之前也有博主做过笔记,我来补充一些自己的理解. 我看有人记了笔记,还不错:http://www.3 ...

  7. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  8. Effective C++ —— 构造/析构/赋值运算(二)

    条款05 : 了解C++默默编写并调用哪些函数 编译器可以暗自为class创建default构造函数.copy构造函数.copy assignment操作符,以及析构函数. 1. default构造函 ...

  9. Effective java读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...

随机推荐

  1. 【微信开发】LINUX-windows下用navicat远程链接虚拟机Linux下MySQL数据库

    今天想用navicat远程连接虚拟机中的MySQL数据库,一直连不上,在网上搜索了一下,发现原因是MySQL对远程用户登陆的授权问题.   MySQL登陆:mysql -h主机地址 -u用户名-p用户 ...

  2. iOS之利用runtime,避免可变数组和可变字典为nil或者数组越界导致的崩溃

    NSArray.NSMutableArray.NSDictionary.NSMutableDictionary.是我们的在iOS开发中非常常用的类.当然,在享受这些类的便利的同时,它们也给我们带来一些 ...

  3. grid 布局的使用

    grid 布局的使用 css 网格布局,是一种二维布局系统. 浏览器支持情况:老旧浏览器不支持, 概念: 网格容器.元素应用dispalay:grid,它是所有网格项的父元素. <div cla ...

  4. JAVA基础之控制台输入输出

    ---恢复内容开始--- 输入需要用scanner机制 代码: 启用scanner机制 Scanner input = new Scanner(System.in); //String x= inpu ...

  5. redux和react-redux的使用详解

    我自己的理解redux就跟vue中的vuex差不多,都是数据管理器,话不多说,我们从经典的计数器案例开始讲解 使用redux实现计数器 创建如下的react项目,我习惯把每一个模块分块,才有这么多文件 ...

  6. 爬虫——Scrapy框架案例二:阳光问政平台

    阳光热线问政平台 URL地址:http://wz.sun0769.com/index.php/question/questionType?type=4&page= 爬取字段:帖子的编号.投诉类 ...

  7. md5加密+盐方式一

    这种方法是采用随机生成盐值加入password中组合成的新密码,下面是md5+盐的一个工具类,直接导入使用即可! 工具类 package com.oracle.utils; import java.s ...

  8. 大数据学习--day07(冒泡排序、Arrays工具类、方法可变参数)

    冒泡排序.Arrays工具类.方法可变参数 冒泡排序之前写过,略 Arrays工具类 二分法查询 public static int binarySearch(int[] a,int key) 头信息 ...

  9. Python学习:7.文件操作

    文件操作 我们曾将听过一个问题,将大象放入冰箱分为三步:1.打开冰箱门,2.将大象放进去,3.关上冰箱门.今天我们要讲的Python文件操作的步骤就像将大象放入冰箱的步骤一样. 使用Python操作文 ...

  10. Netty示例

    一,服务端 ** * 测试Netty类库:服务端代码 * Created by LiuHuiChao on 2016/10/24. */ public class NettyServerTest { ...