问题描述-阻止对象的拷贝

现实生活中的房产中介卖房子,一个服务于这个中介的软件系统很自然的会有一个表示要被销售的房屋的类:

 class HomeForSale { ... };

每个房产中介会立刻指出来,要销售房屋的每个属性都是唯一的,没有两个完全一样的房屋。在这种情况下,拷贝一个HomeForSale对象就没有任何意义了。你在怎么能拷贝一些独一无二的东西呢?因此你可能会尝试,如果有拷贝HomeForSale对象的函数,代码将不能够通过编译。

 HomeForSale h1;

 HomeForSale h2;

 HomeForSale h3(h1); // attempt to copy h1 — should

 // not compile!

 h1 = h2; // attempt to copy h2 — should

 // not compile!

阻止这样的编译不是简简单单能够做到的。在通常情况下,如果你不想一个类支持特定类别的函数,你只要不声明这个函数就可以了。但是这个策略对拷贝构造函数和拷贝赋值运算符来说就不工作了,因为正如Item5指出的:如果你没有声明这两个函数的情况下,如果一些人尝试去调用它们,编译器会自动声明它们。

你遇到麻烦了,如果你没有声明拷贝构造函数或者拷贝赋值运算符,编译器会为你生成。你的类于是会支持拷贝。如果你声明了它们,你的类还是会支持它们。但是你的目标是阻止拷贝!

阻止对象拷贝方法一-将拷贝构造函数和赋值运算符声明为private,并且不去实现它们

解决方案的关键在于所有编译器生成的函数都是public的。为了阻止这些函数被生成,你必须自己声明它们,但是不需要你将它们声明成public的。而是把拷贝构造函数和拷贝赋值运算符声明成private。通过显示的声明一个函数,就会阻止编译器生成它们自己的版本,同时,通过将函数声明成private,你也能够阻止人来调用它们。

这个方法不是十分安全的,因为类成员函数和友元函数仍然能够访问你的私有函数。除非你够聪明而不去定义这些函数。这样如果有人无意调用它们,就会得到一个链接错误。将函数声明成private而不去定义它的诡计如此被大家接受,它常常用在c++ 的iostreams库中,用于阻止类对象之间的拷贝。你可以看一下,在你的标准库的实现中,ios_base,basic_ios和sentry的定义处。你就会发现在每种情况中,拷贝构造函数和拷贝赋值运算符都被声明成private的并且没有被定义。

将这个伎俩用于HomeSale很简单:

 1 class HomeForSale {
2
3 public:
4
5 ...
6
7 private:
8
9 ...
10
11 HomeForSale(const HomeForSale&); // declarations only
12
13 HomeForSale& operator=(const HomeForSale&);
14
15 };

你会发现这里我将函数的参数省略掉了。这不是必须的,只是一个约定俗成的东西。毕竟,这个函数永远不会被实现,更不用说被使用了,因此指定函数参数没有什么用处。

使用上面的类定义,编译器就会阻止客户端尝试拷贝HomeForSale对象,如果你无意的在一个成员函数或者友元函数中这么做了,连接器会发出抱怨。

阻止对象拷贝方法二-将函数的私有声明提到特定基类

将拷贝构造函数和拷贝赋值运算符声明为private的位置由HomeForSale类替换为一个专门设计的基类,同样能够阻止拷贝,并且可以将链接才能发现的错误移动到在编译期就能够发现(这是好事,早点发现错误比晚发现要好)。

 class Uncopyable {

 protected: // allow construction

 Uncopyable() {} // and destruction of

 ~Uncopyable() {} // derived objects...

 private:

 Uncopyable(const Uncopyable&); // ...but prevent copying

 Uncopyable& operator=(const Uncopyable&);

 };

为了阻止HomeForSale被拷贝,我们需要做的是继承Uncopyable.

 class HomeForSale: private Uncopyable { // class no longer

 ... // declares copy ctor or

 }; // copy assign. Operator

这个方法也是行得通的,因为编译器会尝试生成一个拷贝构造函数和一个拷贝赋值运算符如果任何人(包括成员函数或者友元函数)尝试拷贝一个HomeForSale对象。在Item12中解释到,这些函数的编译器生成版本会调用基类的对应的部分,这样调用就会被拒绝,因为基类中的拷贝操作都是private的。

Uncopyable的一些使用和实现有一些微妙的地方,像从Upcopyable继承不必是public继承(Item32和Item39)Uncopyable的析构函数不必是虚函数。因为Uncopyable没有包含任何数据,因此符合Item39描述的,它是进行空基类优化(empty base class optimization)的合格者.一般来说,你可以忽略这些微妙之处,按照展示的使用就可以了,它会恰到好处的工作。你也可以使用Boost库中的版本,名字叫做noncopuable。这是一个很好的类,只是名字看上去不太自然。

读书笔记 effective c++ Item 6 如果你不想使用编译器自动生成的函数,你需要明确拒绝的更多相关文章

  1. Effective C++ -----条款06:若不想使用编译器自动生成的函数,就该明确拒绝

    为驳回编译器自动提供的功能,可将相应的成员函数声明为private并且不予实现. 使用像Uncopyable这样的base class也是一种做法(即先声明一个基类,然后私有继承它).这其实有点像使用 ...

  2. 【Effective c++】条款6:若不想使用编译器自动生成的函数就应该明确拒绝

    地产中介卖的是房子,其使用的中介软件系统应该有个类用来描述卖掉的房子 class HomeFoeSale { ......} 但是任何房子都是独一无二的,不应该存在两个房子拥有同样的属性,因此以下操作 ...

  3. Effective C++ 条款六 若不想使用编译器自动生成的函数,就该明确拒绝

    class HomeForSale //防止别人拷贝方法一:将相应的成员函数声明为private并且不予实现 { public: private: HomeForSale(const HomeForS ...

  4. Effective C++ 之 Item 6 : 若不想使用编译器自动生成的函数,就该明确拒绝

    Effective C++ chapter 2. 构造 / 析构 / 赋值运算 (Constructors, Destructors, and Assignment Operators) Item 6 ...

  5. Effective C++学习笔记 条款06:如不想使用编译器自动生成的函数,就该明确拒绝

    一.为驳回编译器自动提供的机能,可将相应成员函数声明为private并且不予实现.(如果你仅仅是自己不实现的话,编译器会帮你实现) 如: class A { public: A(const strin ...

  6. Effective C++_笔记_条款06_若不想使用编译器自动生成的函数,就该明确拒绝

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 通常如果你不希望class支持某一特定机能,只要不声明对应函数就 ...

  7. Effective C++ 条款06:若不想使用编译器自动生成的函数,就该明确拒绝

    规则一 将成员函数声明为private而且故意不实现他们 class HomeForSale { public: ... private: ... HomeForSale(const HomeForS ...

  8. 读书笔记 effective c++ Item 52 如果你实现了placement new,你也要实现placement delete

    1. 调用普通版本的operator new抛出异常会发生什么? Placement new和placement delete不是C++动物园中最常遇到的猛兽,所以你不用担心你对它们不熟悉.当你像下面 ...

  9. 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库

    1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...

随机推荐

  1. Sping--IOC概念

    1. 新建项目, 引入spring包(sping, common-annotation, common-logging包), 还有junit包. user.java: package com.bjsx ...

  2. MQ-2烟雾传感器启动

    MQ-2气体传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡(SnO2).当传感器所处环境中 存在可燃气体时,传感器的电导率随空气中可燃气体浓度的增加而增大.使用简单的电路即可将电导率的 变化 ...

  3. NavigationControllerr滑动返回

    iOS 7中在传统的左上角返回键之外,提供了右滑返回上一级界面的手势.支持此手势的是UINavigationController中新增的属性 interactivePopGestureRecogniz ...

  4. js执行js字符串函数的方法

    <script> var jsText = 'return function(){alert(1+1)}' var jscode = new Function(jsText)(); jsc ...

  5. linux内核——1.概述

    1.结构 linux中,我们把操作系统分为内核空间和用户空间.用户通过用户空间与操作系统打交道.用户要通过系统调用访问内核空间.下图为Linux体系结构,shell应该为在最顶层. 系统调用,下面链接 ...

  6. Android studio的那些bug

    R文件无法编译: 1.Activity继承类AppCompatActivity有时候会报错 2.R文件找不到一般是layout文件夹下出错 3.sdk包问题 4.Gradle DSL method n ...

  7. Struts2接受参数的几种类型和接受复杂类型参数(list<String>和list<Object>)

    Struts2接受参数的几种类型 大概有这几种类型: 1.使用Action的属性接受参数 在Action中加入成员变量,配置Getter和Setter方法,Getter而和Setter方法的名字和表单 ...

  8. eclipse中集成svn maven开发手册---合并主干

    如图,代码提交分支之后 右键,项目,切换到主干版本 切换回主干版本后: 更新主干版本 合并 选择merge a range of revisions,点击next 录入要合并的分支版本的路径.此处的s ...

  9. IOS 上线问题

    info.plist 是否支持后台位置 音频 Info.plist中添加UIBackgroundModes键值,它包含一个或多个string的值,包括 audio:在后台提供声音播放功能,包括音频流和 ...

  10. centos 用户管理

    3.使用命令管理账户 useradd 选项  用户名//添加新用户 usermod 选项  用户名//修改已经存在的用户 userdel -r    用户名//删除用户表示自家目录一起删除. grou ...