本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!

博客已经迁移到这里啦

如果你为其他开发者提供代码,并且你想阻止他们调用一个特定的函数,你通常不会声明这个函数。函数不声明,函数就不会被调用。太简单了!但是有时候C++会帮你声明函数,并且如果你想要阻止客户调用这些函数,简单的事情就不再简单了。

这种情况只发生在“特殊的成员函数”身上,也就是,当你需要这些成员函数的时候,C++会自动帮你生成。Item 17详细地讨论了这些函数,但是现在,我们只考虑copy构造函数和copy assignment operator。这章主要讲的是,用C++11中更好的做法替换在C++98中的常用做法。然后在C++98中,你最想抑制的成员函数,常常是copy构造函数,assignment operator,或都想抑制。

在C++98中,阻止这些函数的方法是:把它们声明成private的,并且不去定义它们。举个例子,C++标准库中的iostream类层次的底层有个class template叫做basic_ios。所有istream和ostream继承自(可能不是直接地)这个类。拷贝istream和ostream是不受欢迎的,因为没有一个清晰的概念规定这些操作应该做些什么。举个例子,一个istream对象表示一些输入值的流,有些值已经被读过了,有些值可能会在之后读入。如果一个istream被拷贝,那么是否有必要拷贝所有之前读过以及以后要读的值呢?处理这个问题的最简单的办法就是,定义它们为不存在的。要做到这点,只需要禁止stream的拷贝就行了。

为了使istream和ostream类不能拷贝,basic_ios在C++98中如此实现(包括注释):

template<class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base{
public:
... private:
basic_ios(const basic_ios&); // not defined
basic_ios& operator=(const basic_ios&); //not defined
};

把这些函数声明为private,可以阻止客户调用它们。故意不去定义它们意味着如果一些代码有权利访问它们(也就是,成员函数或友元类),并使用它们,那么在链接的时候,就会因为找不到函数定义而失败。

在C++11中,有更好的办法,它能在本质上实现所需的功能:使用“=delete”来标记copy 构造函数和copy assignment operator,让它们成为deleted函数。这里给出C++11中的basic_ios的实现:

template<calss charT, class trais = char_traits<charT> >
class basi_ios : public ios_base{
public:
...
basic_ios(const basic_ios&) = delete;
basic_ios& operator= (const basic_ios&) = delete;
...
};

把这些函数“删除掉”和把它们声明为private的不同之处看起来除了更时尚一点就没别的了,但是这里有一些实质上的优点是你没想到的。deleted 函数不会被任何方式使用,所以就算在成员函数和友元函数中,它们如果尝试拷贝basic_ios对象,它们也会失败。比起C++98(这样的错误使用在链接前无法被诊断出来),这算是一个提升。

按照惯例,deleted函数被声明为public,而不是private。这是有原因的。当客户代码尝试使用一个成员函数,C++在检查deleted状态之前,会先检查它的可访问性。当客户代码尝试使用一个deleted private函数,尽管函数的可访问性不会影响到它能否被使用(这个函数总是不可调用的),一些编译器只会“抱怨”出函数是private的。当修改历史遗留的代码,把private-and-not-defined成员函数替换成deleted函数时,尤其要记得这一点(声明deleted函数为public的),因为让新函数成为public的,将产生更好的错误消息。

比起必须要把函数声明为private的,deleted函数还有一个关键的优点,那就是任何函数都可以成为deleted的。举个例子,假设我们有一个非成员函数,这个函数以一个整形为参数,并且返回一个bool表示它是否是幸运数字:

bool isLucky(int number);

C++是从C继承来的,这意味着很多其它类型能被模糊地视为数值类型,然后隐式转换到int,但是一些能通过编译的调用是没有意义的:

if(isLucky('a')) ...			//'a'是一个幸运数字吗?

if(isLucky(true))...			//"true"是幸运数字吗?

if(isLucky(3.5))...				//在检查它的幸运属性前,我们是否应该
//把它截断为3

如果幸运数字必须是整形类型,我们可以阻止上面这些调用。

一种方式是用我们想过滤掉的类型创建deleted重载:

bool isLucky(int number);

isLucky(char) = delete;			//拒绝char

isLucky(bool) =  delete;		//拒绝bool

isLucky(double)	= delete;		//拒绝double和float

(你可能会感到奇怪:double重载版本的注释中说double和float都被拒绝了。只要你记起:给出从float到int以及float到double的转换时,C++会更优先把float转换到double,你的疑问就消散了。因此,用float调用isLucky会调用double版本而不是int版本的重载。好了,它(编译器)会先尝试调用isLucky,事实上这个版本的重载是deleted的,所以在编译时就会阻止这个调用。)

尽管deleted函数不能被使用,它们还是你程序中的一部分。因此,它们在重载解析时,它们会被考虑进去。这就是为什么只要使用上面这样的deleted函数声明式,令人讨厌的调用就被拒绝了:

if(isLucky('a')) ...			//错误,调用一个deleted函数

if(isLucky(true))...			//错误

if(isLucky(3.5))...				//错误

deleted函数还有一个使用技巧(private 成员函数做不到),那就是阻止不需要的template实例。举个例子,假设你需要一个使用built-in指针的template(第四章的建议是,比起raw指针,优先使用智能指针):

template<typename T>
void processPointer(T* ptr);

在指针的世界中,有两种特殊的情况。一种是void*指针,因为他们无法解引用,无法增加或减少,等等。另外一个就是char*指针,因为他们常用来代表指向C风格字符串的指针,而不是指向单个字符的指针。这些特殊的情况常常需要特别处理。现在,在processPointer template中,让我们假设我们需要做的特殊处理是拒绝这些类型的调用。也就是不能使用void*char*指针来调用processPointer。

这很容易执行,只要把他们的实例删除(delete)掉:

template<>
void processPointer<void>(void*) = delete; template<>
void processPointer<char>(char*) = delete;

现在,我们用void*或者char*调用processPointer是无效的,const void*const char*可能也需要是无效的,因此,这些实例也需要被删除(delete):

template<>
void processPointer<const void>(const void*) = delete; template<>
void processPointer<const char>(const char*) = delete;

并且你真想做的很彻底的话,你还需要删除(delete)掉const volatile void*const volatile char*重载,然后你需要再为其他标准字符类型(std::wchar_t, std::char16_t以及std::char32_t)做这样的工作。

有意思的是,如果你有一个函数template内嵌于一个class,然后你想通过把特定的实例声明为private(啊啦,典型的C++98的方法)来使它们无效,这是无法实现的,因为你无法把一个成员函数template特化为不同的访问等级(和主template的访问等级不同)。举个例子,如果processPointer是一个内嵌于Widget的成员函数template,然后你想让void*指针的调用失效,尽管无法通过编译,C++98的方法看起来像是这样:

class Widget{
public:
...
template<typename T>
void processPointer(T* ptr)
{ ... } private:
template<>
void processPointer<void>(void*); //错误
};

问题在于template特化必须写在命名空间的作用域中,而不是类的作用域中。这个问题不会影响deleted函数,因为他们不需要不同的访问等级。他们能在class外面被删除(因此处在命名空间的作用域中):

class Widget{
public:
...
template<typename T>
void processPointer(T* ptr)
{ ... } ...
}; template<>
void Widget::processPointer<void>(void*) = delete;

事实上,C++98中,声明函数为private并不定义它们就是在尝试实现C++11的deleted函数所实现的东西。作为一个模仿,C++98的方法没有做到和实物(C++11的deleted函数)一模一样。它在class外面无法工作,它在class里面不总是起作用,就算它起作用,它在链接前可能不起作用。所以坚持使用deleted函数把!!!

你要记住的事
  • 比起private undefined function优先使用deleted function
  • 任何函数都能被删除(deleted),包括非成员函数和template实例化函数。

item 11: 比起private undefined function优先使用deleted function的更多相关文章

  1. Item 21: 比起直接使用new优先使用std::make_unique和std::make_shared

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 让我们先从std::make_unique和std::make_s ...

  2. Item 13: 比起iterator优先使用const_iterator

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 STL中的const_iterator等价于pointers-to ...

  3. javascript工厂函数(factory function)vs构造函数(constructor function)

    如果你从其他语言转到javascript语言的开发,你会发现有很多让你晕掉的术语,其中工厂函数(factory function)和构造函数(constructor function)就是其中的一个. ...

  4. JavaScript中Function Declaration与Function Expression 或者说 function fn(){}和var fn=function(){} 的区别

    JavaScript是一种解释型语言,函数声明会在JavaScript代码加载后.执行前被解释,而函数表达式只有在执行到这一行代码时才会被解释. 在JS中有两种定义函数的方式, 1是:var aaa= ...

  5. JS 究竟是先有鸡还是有蛋,Object与Function究竟谁出现的更早,Function算不算Function的实例等问题杂谈

    壹 ❀ 引 我在JS 疫情宅在家,学习不能停,七千字长文助你彻底弄懂原型与原型链一文中介绍了JavaScript原型与原型链,以及衍生的__proto__.constructor等一系列属性.在解答了 ...

  6. $(window).load(function() {})和$(document).ready(function(){})的区别

    JavaScript 中的以下代码 : Window.onload = function (){// 代码 }  等价于  Jquery 代码如下: $(window).load(function ( ...

  7. JQuery $(function(){})和$(document).ready(function(){})

    document.ready和onload的区别——JavaScript文档加载完成事件页面加载完成有两种事件一是ready,表示文档结构已经加载完成(不包含图片等非文字媒体文件)二是onload,指 ...

  8. $(function(){})和$(document).ready(function(){}) 的用法

    当文档载入完毕就执行,以下几种效果是等价的:1. $(function(){ //这个就是jQuery ready()的简写,即下2的简写 // do something }); 2. $(docum ...

  9. $(function(){})和$(document).ready(function(){})

    document.ready和onload的区别——JavaScript文档加载完成事件 页面加载完成有两种事件 一是ready,表示文档结构已经加载完成(不包含图片等非文字媒体文件) 二是onloa ...

随机推荐

  1. kNN处理iris数据集-使用交叉验证方法确定最优 k 值

    基本流程: 1.计算测试实例到所有训练集实例的距离: 2.对所有的距离进行排序,找到k个最近的邻居: 3.对k个近邻对应的结果进行合并,再排序,返回出现次数最多的那个结果. 交叉验证: 对每一个k,使 ...

  2. mybatis学习系列三(部分)

    1 forearch_oracle下批量保存(47) oracle批量插入 不支持values(),(),()方式 1.多个insert放在begin-end里面 begin insert into ...

  3. [20180608]Wrong Results with IOT, Added Column and Secondary Index.txt

    [20180608]Wrong Results with IOT, Added Column and Secondary Index.txt --//链接:http://db-oriented.com ...

  4. 使用wxpy自动发送微信消息

    思路整理:1.进入心灵鸡汤网页,使用python获取心灵鸡汤内容 2.登陆微信,找到需要发送的朋友 3.发送获取的内容 1.获取心灵鸡汤的内容 如下图,获取第一条鸡汤 实现如下: 2.登陆微信,搜索朋 ...

  5. ELK-logstash-6.3.2部署

    Logstash 是一款强大的数据处理工具,它可以实现数据传输,格式处理,格式化输出,还有强大的插件功能,常用于日志处理. 1. logstash部署 [yun@mini04 software]$ p ...

  6. Sqoop-1.4.7-部署与常见案例

    该文章是基于 Hadoop2.7.6_01_部署 . Hive-1.2.1_01_安装部署 进行的 1. 前言 在一个完整的大数据处理系统中,除了hdfs+mapreduce+hive组成分析系统的核 ...

  7. .net core 入坑经验 - 3、MVC Core之jQuery不能使用了?

    在View中添加了一段jQuery代码用来控制一个按钮的点击事件.发现运行时提示$对象没有定义,经过在浏览器右键查看源文件发现,script代码在引用jquery代码的上方,执行时jquery还未引入 ...

  8. Python getting started guide

    Get up in the morning. The first thing is to write a blog, although it uses machine translation, it ...

  9. 17秋 软件工程 团队第五次作业 Alpha Scrum7

    17秋 软件工程 团队第五次作业 Alpha Scrum7 今日完成的任务 世强:部员详情列表的编写与数据交互,完善APP通知模块: 港晨:完成前端登陆界面编写: 树民:完善Web后端数据库访问模块: ...

  10. Beta冲刺(5/5)(麻瓜制造者)

    今日已完成 邓弘立:完成了图书馆新功能 符天愉:完成管理员用户查询,删除商品/需求以及注销功能 江郑:进行后台管理员的web开发 刘双玉:修改了商品搜索中数据返回类型不对的错误,添加了图书馆查询接口 ...