详解在C++中显式默认设置的函数和已删除的函数的方法

在 C++11 中,默认函数和已删除函数使你可以显式控制是否自动生成特殊成员函数。已删除的函数还可为您提供简单语言,以防止所有类型的函数(特殊成员函数和普通成员函数以及非成员函数)的参数中出现有问题的类型提升,这会导致意外的函数调用。
显式默认设置的函数和已删除函数的好处
在 C++ 中,如果某个类型未声明它本身,则编译器将自动为该类型生成默认构造函数、复制构造函数、复制赋值运算符和析构函数。这些函数称为特殊成员函数,它们使 C++ 中的简单用户定义类型的行为如同 C 中的结构。也就是说,可以创建、复制和销毁它们而无需任何额外编码工作。C++11 会将移动语义引入语言中,并将移动构造函数和移动赋值运算符添加到编译器可自动生成的特殊成员函数的列表中。
这对于简单类型非常方便,但是复杂类型通常自己定义一个或多个特殊成员函数,这可以阻止自动生成其他特殊成员函数。实践操作:

  • 如果显式声明了任何构造函数,则不会自动生成默认构造函数。
  • 如果显式声明了虚拟析构函数,则不会自动生成默认析构函数。
  • 如果显式声明了移动构造函数或移动赋值运算符,则:
  1. 不自动生成复制构造函数。
  2. 不自动生成复制赋值运算符。
  • 如果显式声明了复制构造函数、复制赋值运算符、移动构造函数、移动赋值运算符或析构函数,则:
  1. 不自动生成移动构造函数。
  2. 不自动生成移动赋值运算符。

注意

此外,C++11 标准指定将以下附加规则:

  • 如果显式声明了复制构造函数或析构函数,则弃用复制赋值运算符的自动生成。
  • 如果显式声明了复制赋值运算符或析构函数,则弃用复制构造函数的自动生成。
  • 在这两种情况下,Visual Studio 将继续隐式自动生成所需的函数且不发出警告。

这些规则的结果也可能泄漏到对象层次结构中。例如,如果基类因某种原因无法拥有可从派生类调用的默认构造函数 - 也就是说,一个不采用任何参数的 public 或 protected 构造函数 - 那么从基类派生的类将无法自动生成它自己的默认构造函数。

这些规则可能会使本应直接的内容、用户定义类型和常见 C++ 惯例的实现变得复杂 — 例如,通过以私有方式复制构造函数和复制赋值运算符,而不定义它们,使用户定义类型不可复制。

struct noncopyable
{
 noncopyable() {};

private:
 noncopyable(const noncopyable&);
 noncopyable& operator=(const noncopyable&);
};

在 C++11 之前,此代码段是不可复制的类型的惯例形式。但是,它具有几个问题:
复制构造函数必须以私有方式进行声明以隐藏它,但因为它进行了完全声明,所以会阻止自动生成默认构造函数。如果你需要默认构造函数,则必须显式定义一个(即使它不执行任何操作)。
即使显式定义的默认构造函数不执行任何操作,编译器也会将它视为重要内容。其效率低于自动生成的默认构造函数,并且会阻止 noncopyable 成为真正的 POD 类型。
尽管复制构造函数和复制赋值运算符在外部代码中是隐藏的,但成员函数和 noncopyable 的友元仍可以看见并调用它们。如果它们进行了声明但是未定义,则调用它们会导致链接器错误。
虽然这是广为接受的惯例,但是除非你了解用于自动生成特殊成员函数的所有规则,否则意图不明确。
在 C++11 中,不可复制的习语可通过更直接的方法实现。

struct noncopyable
{
 noncopyable() =default;
 noncopyable(const noncopyable&) =delete;
 noncopyable& operator=(const noncopyable&) =delete;
};

请注意如何解决与 C++11 之前的惯例有关的问题:
仍可通过声明复制构造函数来阻止生成默认构造函数,但可通过将其显式设置为默认值进行恢复。
显式设置的默认特殊成员函数仍被视为不重要的,因此性能不会下降,并且不会阻止 noncopyable 成为真正的 POD 类型。
复制构造函数和复制赋值运算符是公共的,但是已删除。定义或调用已删除函数是编译时错误。
对于了解 =default 和 =delete 的人来说,意图是非常清楚的。你不必了解用于自动生成特殊成员函数的规则。
对于创建不可移动、只能动态分配或无法动态分配的用户定义类型,存在类似惯例。所有这些惯例都具有 C++11 之前的实现,这些实现会遭受类似问题,并且可在 C++11 中通过按照默认和已删除特殊成员函数实现它们,以类似方式进行解决。
显式默认设置的函数
可以默认设置任何特殊成员函数 — 以显式声明特殊成员函数使用默认实现、定义具有非公共访问限定符的特殊成员函数或恢复其他情况下被阻止其自动生成的特殊成员函数。
可通过如此示例所示进行声明来默认设置特殊成员函数:

struct widget
{
 widget()=default;

 inline widget& operator=(const widget&);
};

inline widget& widget::operator=(const widget&) =default;

请注意,只要特殊成员函数可内联,便可以在类主体外部默认设置它。
由于普通特殊成员函数的性能优势,因此我们建议你在需要默认行为时首选自动生成的特殊成员函数而不是空函数体。你可以通过显式默认设置特殊成员函数,或通过不声明它(也不声明其他会阻止它自动生成的特殊成员函数),来实现此目的。
注意
Visual Studio 不支持默认的移动构造函数或移动赋值运算符作为 C++11 标准授权。有关详细信息,请参阅 支持 C++11/14/17 功能(现代 C++)中的“默认函数和已删除的函数”一节。
已删除的函数
可以删除特殊成员函数以及普通成员函数和非成员函数,以阻止定义或调用它们。通过删除特殊成员函数,可以更简洁地阻止编译器生成不需要的特殊成员函数。必须在声明函数时将其删除;不能在这之后通过声明一个函数然后不再使用的方式来将其删除。

struct widget
{
 // deleted operator new prevents widget from being dynamically allocated.
 void* operator new(std::size_t) = delete;
};

删除普通成员函数或非成员函数可阻止有问题的类型提升导致调用意外函数。这可发挥作用的原因是,已删除的函数仍参与重载决策,并提供比提升类型之后可能调用的函数更好的匹配。函数调用将解析为更具体的但可删除的函数,并会导致编译器错误。

// deleted overload prevents call through type promotion of float to double from succeeding.
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }

请注意,在前面的示例中,使用 call_with_true_double_only 参数调用 float 将导致编译器错误,但使用 call_with_true_double_only 参数调用 int 不会导致编译器错误;在 int 示例中,此参数将从 int 提升到 double,并成功调用函数的 double 版本,即使这可能并不是预期目的。若要确保使用非双精度参数对此函数进行的任何调用均会导致编译器错误,您可以声明已删除的函数的模板版本。

template < typename T >

void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.

void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.
(0)

相关推荐

  • 解析C++中多层派生时的构造函数及一些特殊形式

    C++多层派生时的构造函数 一个类不仅可以派生出一个派生类,派生类还可以继续派生,形成派生的层次结构.在上面叙述的基础上,不难写出在多级派生情况下派生类的构造函数. 通过例下面的程序,读者可以了解在多级派生情况下怎样定义派生类的构造函数.相信大家完全可以自己看懂这个程序. [例] 多级派生情况下派生类的构造函数. #include <iostream> #include<string> using namespace std; class Student//声明基类 { publi

  • VC++ 使用 _access函数判断文件或文件夹是否存在

    _access函数 int _access( const char *path, int mode ); int _waccess( const wchar_t *path, int mode ); 参数 path 文件或目录路径. mode 读/写特性. 返回值   如果文件包含特定模式,每个函数返回 0. 函数返回 - 1,则名称文件不存在或不具有特定模式:在这种情况下,如下表errno 所示设置. EACCES 访问被拒绝:文件权限的设置不允许指定的访问权限. ENOENT 未找到文件名或

  • 深度探究C++中的函数重载的用法

    C++ 允许同一范围内具有相同名称的多个函数的规范.这些函数称为重载函数,"重载"中对其进行了详细介绍.利用重载函数,程序员可以根据参数的类型和数量为函数提供不同的语义. 例如,采用字符串(或 char *)参数的 print 函数执行的任务与采用"双精度"类型的参数的函数执行的任务截然不同.重载允许通用命名并使程序员无需创建名称,例如 print_sz 或 print_d.下表显示了 C++ 使用函数声明的哪些部分来区分同一范围内具有相同名称的函数组. 重载注意事

  • 实例讲解在C++的函数中变量参数及默认参数的使用

    包含变量参数列表的函数 如果函数声明中最后一个成员是省略号 (...),则函数声明可采用数量可变的参数.在这些情况下,C++ 只为显式声明的参数提供类型检查.即使参数的数量和类型是可变的,在需要使函数泛化时也可使用变量参数列表.函数的系列是一个使用变量参数列表的函数的示例.printfargument-declaration-list 包含变量参数的函数 若要访问声明后的参数,请使用包含在标准包含文件 STDARG.H 中的宏(如下所述). 采用数量可变的参数的函数声明至少需要一个占位符参数(即

  • C++中strcpy函数的实现

    我们先来看个例子 char * strcpy(char * strDest,const char * strSrc) { if ((NULL==strDest) || (NULL==strSrc)) throw "Invalid argument(s)"; char * strDestCopy = strDest; while ((*strDestCopy++ = *strSrc++) != '\0'); return strDest; } 突然想到之前做过的一个试题 题目:    

  • C++编程中队内联函数的理解和使用

    函数调用过程 c++经过编译生成可执行程序文件exe,存放在外存储器中.程序启动,系统从外存储器中将可执行文件装载到内存中,从入口地址(main函数起始处)开始执行.程序执行中遇到了对其他函数的调用,就暂停当前函数的执行,并保存下一条指令的地址作为从被调函数返回后继续执行的入口点,保存现场.然后转到被调函数的入口地址执行被调函数.遇到return语句或者被调函数结束后,恢复先前保存的现场,从先前保存的返回地址处继续执行主调函数的其余部分. 内联函数 函数调用需要进行现场保护,以便在函数调用之后继

  • C++中函数使用的基本知识学习教程

    函数是执行某种操作的代码块.函数可以选择性地定义使调用方可以将实参传递到函数中的输入形参.函数可以选择性地返回值作为输出.函数可用于在单个可重用块中封装常用操作(理想情况是使用可清晰地描述函数行为的名称).以下函数从调用方接受两个整数并返回其总和:a 和 b 是 int 类型的参数. int sum(int a, int b) { return a + b; } 可以从程序中任意数量的位置调用函数.传递给函数的值是实参,其类型必须与函数定义中的形参类型兼容. int main() { int i

  • C++中replace()函数使用方法汇总

    C++编程语言中的string应用方式多样化,每一种应用方式都能帮助我们提实现特定的功能需求.在这里我们将会为大家详细介绍一下其中一个比较重要的用法,有关C++ replace()函数的应用方式. basic_string::max_size C++ replace()函数返回string 能放的最大元素个数.(不同于capacity) size _ type max _ size( ) const; basic_string <char>::size_type cap, max; cap =

  • 深入解析C++中派生类的构造函数

    基类的构造函数不能被继承,在声明派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数来完成.所以在设计派生类的构造函数时,不仅要考虑派生类新增的成员变量,还要考虑基类的成员变量,要让它们都被初始化. 解决这个问题的思路是:在执行派生类的构造函数时,调用基类的构造函数. 下面的例子展示了如何在派生类的构造函数中调用基类的构造函数. #include<iostream> using namespace std; //基类 class People{ protected: char *n

  • 详解在C++中显式默认设置的函数和已删除的函数的方法

    在 C++11 中,默认函数和已删除函数使你可以显式控制是否自动生成特殊成员函数.已删除的函数还可为您提供简单语言,以防止所有类型的函数(特殊成员函数和普通成员函数以及非成员函数)的参数中出现有问题的类型提升,这会导致意外的函数调用. 显式默认设置的函数和已删除函数的好处 在 C++ 中,如果某个类型未声明它本身,则编译器将自动为该类型生成默认构造函数.复制构造函数.复制赋值运算符和析构函数.这些函数称为特殊成员函数,它们使 C++ 中的简单用户定义类型的行为如同 C 中的结构.也就是说,可以创

  • 详解修改Anaconda中的Jupyter Notebook默认工作路径的三种方式

    方式1. 打开Windows的cmd,在cmd中输入jupyter notebook --generate-config如下图: 可以看到路径为D:\Users--找到此路径修改jupyter_notebook_config.py文件 打开此文件找到 ## The directory to use for notebooks and kernels. #c.NotebookApp.notebook_dir = '' 将其改为 ## The directory to use for noteboo

  • 详解JAVA Spring 中的事件机制

    说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知listener,从而针对变化做相应的动作.这些listener是怎么实现的呢?说listener之前,我们先从设计模式开始讲起. 观察者模式 观察者模式一般包含以下几个对象: Subject:被观察的对象.它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify().目标类可以是接口,也可以是抽象类或具体类. ConcreteSubject:具体的

  • 详解nginx.conf 中 root 目录设置问题

    在配置 nginx.conf 总会遇到一些问题,下面列举一些常见的问题并说明如何解决 1.相对路径的问题 例如配置文件中 location 设置 location ~ .php${ root html } location 中root所指向的html是一个相对路径,相对的是这个配置文件的路径,假设此配置文件的位置是/etc/nginx/conf.d,那么这个html的绝对路径就是/etc/nginx/conf.d/html.因此为避免出现不必要的麻烦,在配置root路径的过程中最好用绝对路径.

  • 详解在SpringBoot中@Transactional事物操作和事物无效问题排查

    目录 1.spring事务管理简述 2.SpringBoot中使用@Transactional注解 2.1.开启事务注解 2.2.在目标类.方法上添加注解@Transactional 2.3.细化事务配置 3.@Transactional事务实现机制 3.1.整体事务控制流程 3.2.Spring AOP的两种代理 3.3.事务操作的底层实现 4.@Transactional使用注释实现及问题排查 4.1.数据库引擎是否支持事务? 4.3.注解所在的类是否被加载成Bean? 4.2.注解所在方法

  • 详解C++11中的线程库

    目录 一.线程库的介绍 1.1. 使用时的注意点 1.2. 线程函数参数 1.3. join与detach 二.原子性操作库 2.1. atomic 2.2. 锁 三.使用lambda表达式创建多个线程 四.条件变量 一.线程库的介绍 在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差.C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念.要使用标

  • 详解Go语言中的数据类型及类型转换

    目录 1.基本数据类型 2.基础数据类型转换 3.基本数据类型转为字符串 4.strconv的使用 5.字符串转为基础类型 1.基本数据类型 数据类型有很多,先研究一下基础的,例如:布尔型.数字类型.字符串类型. 数字类型有uint8.uint16.uint32.uint64.int8.int16.int32.int64(uint和int区别在于uint为无符号整数,即只支持正数,不支持负数形式) 数字浮点型有fload32.float64.complex64.complex126(后面两个均为

  • 详解Java String中intern方法的原理与使用

    目录 简介 常量池简介 intern方法简介(JDK7) 原理(JDK6与JDK7) 例程测试 例程分析 jdk1.6 jdk1.7 应用实例 简介 本文介绍Java的String的intern方法的原理. 常量池简介 在 JAVA 语言中有8种基本类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池(在方法区)的概念.常量池就类似一个JAVA系统级别提供的缓存.8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊. Str

  • 详解CSS样式中的!important、*、_符号

    详解CSS样式中的!important.*._符号 !important.*._其实没什么用,皆是用来设置样式的优先级,但是样式的优先级你可以自行排好其先后位置来设置,然而你还是要看懂的. 我们知道,CSS写在不同的地方有不同的优先级, .css文件中的定义 < 元素style中的属性,但是如果使用!important,事情就会变得不一样. 首先,先看下面一段代码: <!DOCTYPE HTML> <html> <head> <meta http-equiv

  • 详解在WebStorm中添加Vue.js单文件组件的高亮及语法支持

    本文介绍了详解在WebStorm中添加Vue.js单文件组件的高亮及语法支持,分享给大家,具体如下: 一个小遗憾 能来看这篇文章的想必不用我来介绍vue是什么了.先让我们膜拜大神!vue项目的创建者尤大写了个sublime下语法高亮的插件,有人问他how about webstorm support?他是这么回答的.默哀一分钟. 添加高亮和语法支持 这个我是通过插件来实现的.网上目前有两个插件: 插件1:https://github.com/henjue/vue-for-idea 插件2:htt

随机推荐