C++类的特殊成员函数及default/delete特性

本文内容侧重个人理解,深入理解其原理推荐https://www.geeksforgeeks.org

目录

目录

C++类的特殊成员函数及default/delete特性

前言

1. 构造函数和拷贝构造函数

2. 拷贝赋值运算符

3. C++11特性之default关键字(P237, P449)

4. C++11特性之delete关键字(P449):使用“=delete”来限制函数生成

参考



前言

拷贝,赋值与构造 详见

C++ 的类有四类特殊成员函数,分别是:

  • 默认构造函数

  • 析构函数

  • 拷贝构造函数

  • 拷贝赋值运算符

这些类的特殊成员函数负责创建、初始化、销毁,或者拷贝类的对象。如果没有显式地为一个类定义某个特殊成员函数,而又需要用到该特殊成员函数时,则编译器会隐式的为这个类生成一个默认的特殊成员函数。

sample

class Test{private:int _id;
​public:Test(int a);  // 构造函数Test(const Test& test);  // 拷贝构造函数Test& operator=(const Test& test);  // 拷贝赋值运算符~Test();  // 析构函数
}

1. 构造函数和拷贝构造函数

从名称上也不难理解,构造函数拷贝构造函数都是用于进行类实例初始化的

Test t1(2);  // 调用实参匹配的构造函数
Test t2 = t1;  // 调用拷贝构造函数,用t1对t2进行初始化

以上也正是C++类进行初始化的两种方式。

使用/调用拷贝构造函数的情况

  拷贝构造函数的作用主要是复制对象

  1. 复制对象,并通过函数返回复制后的对象。

  2. 一个对象以值传递的方式传入函数,此时必定调用拷贝构造函数,即void Func(Test test){}这类。

  3. 一个对象以另一个对象为例进行初始化,以上Test t2=t1;即是此例。

必须显式定义拷贝构造函数的情况

   对于拷贝来讲,尤其需要注意的即是指针和动态分配的资源,这些很容易造成拷贝变成浅拷贝(指向复制前后的变量指向同一块内存区域)。   因此如果类的成员变量包含指针类型,或者有成员表示在构造函数中分配的其他资源,这两种情况下都必须显式的定义拷贝构造函数。

2. 拷贝赋值运算符

 通过定义拷贝赋值运算符,来实现类实例之间的=拷贝运算。 拷贝赋值运算符的通常形式为 classname& operator=(const classname& a)

拷贝赋值运算符与拷贝构造函数 以下例来观察二者的不同:

Test t2 = t1; // 即上面的例子,调用的是拷贝构造函数,即在创建时进行初始化
Test t3;
t3 = t1;  // 此时调用拷贝赋值运算符,因为并不是在创建类的实例时进行初始化

 可以看出二者之间有很大的共通性,即都是为了进行完整的复制/拷贝而创立的,防止陷入浅拷贝造成内存安全问题。

3. C++11特性之default关键字(P237, P449)

类内是哦那个=default修饰成员变量时候,合成的函数隐式声明成内联函数。如果不希望合成的成员是内联函数,应该只对成员的类外定义使用=default。

=default含义:

当我们定义这个构造函数的目的仅仅是我们既需要其他形式的构造函数,也需要默认的构造函数,我们希望这个函数的作用等于之前使用的默认构造函数。如下

struct Sales_data{Sales_data()=default;Sales_data(const std::string &s):bookNo(s){}Sales_data(const std::string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(P*n){}Sales_data(const std::istream &)... ...}

构造函数初始化列表

Sales_data(const std::string &s):bookNo(s),units_sold(0),revenue(0){}//

 在未显式的定义类的特殊成员函数时,如果被调用,系统会自动隐式的创建该特殊成员函数,且隐式的创建方式比显式的创建方式执行效率高

 只需在函数声明后加上=default;,就可将该函数声明为 defaulted 函数,编译器将为显式声明的 defaulted 函数自动生成函数体,以获得更高的执行效率。

 有些时候,我们需要禁用某些(通常用法为禁用类的成员函数,如单例模式中的拷贝构造函数,与赋值函数)函数(=delete不仅可以禁用类内的特殊成员函数,也可以禁用一般函数),此时就需要在该函数后面增加=delete;,则该函数将变的不可调用,比如不可复制等。

sample

class Test{private:int _id;
​public:Test() = default; // 定义默认构造函数Test(int a);Test(const Test& test) = delete;  // 禁止使用拷贝构造函数的场景Test& operator=(const Test& test);~Test();
}

4. C++11特性之delete关键字(P449):使用“=delete”来限制函数生成

本质上,这些规则的含义是:如果一个类有数据成员不能默认构造函数,拷贝,赋值,或销毁,则对应的成员函数将被定义为删除。

常见实例应用为单例模式中的权限访问设置。https://guoqiang.blog.csdn.net/article/details/115452089

本质上,当不可能拷贝 赋值 或者销毁类的成员时,类的合成拷贝控制成员就被定义为删除。

  • 实例1: Employee 类需要定义自己的开呗控制函数吗?为什么?P452  不需要,因为真的没有任何合理的意义。员工不能在现实世界中模仿别人。
#include <string>
using std::string;class Employee {
public:Employee();Employee(const string &name);Employee(const Employee&) = delete;Employee& operator=(const Employee&) = delete;const int id() const { return id_; }private:string name_;int id_;static int s_increment;
};
  • 实例2: 我们在类中实现了这些版本之后,编译器便不会生成其对应的默认函数版本,这时需要我们显式的写上其对应的默认函数版本。  
#include<iostream>
using namespace std;
class Student
{
public:Student(const int a,const int b):m_a(a),m_b(b){
​}
​
int getA()const{return m_a;}
int getB()const{return m_b;}
private:
int m_a;
int m_b;
};
​
int main(int argc,char **argv)
{
Student stu(1,2);
cout<<stu.getA()<<endl; //1
cout<<stu.getB()<<endl; //2
​Student stu1;           //编译失败,报错: no matching function for call to ‘Student::Student()’
​
return 0;
}
​
编译方式:g++ Student.cpp

例1定义了一个对象stu1,该对象将会使用Student类的无参构造函数,而该默认构造函数在Student类中,我们没有显式的说明。因此,c++编译器在我们提供了该函数实现之后是不会生成与之对应的默认函数版本的。在Student中我们重载了带2个参数的构造函数,但是无参的构造函数,没有提供,因此会报错。

  解决方式是:在该类中显式的提供无参构造函数,如下:

  • 实例3:
#include<iostream>
using namespace std;
class Student
{
public:Student(){}   //显式说明Student的无参构造函数Student(const int a,const int b):m_a(a),m_b(b){
​}
​
int getA()const{return m_a;}
int getB()const{return m_b;}
private:
int m_a;
int m_b;
};
​
int main(int argc,char **argv)
{
Student stu(1,2);
cout<<stu.getA()<<endl; //1
cout<<stu.getB()<<endl; //2
​Student stu1;
return 0;
}

问题:以 Student(){} 这样的方式来声明无参数构造函数,会带来一个问题,就是使得 其不再是 POD 类型,因此可能让编译器失去对这样的数据类型的优化功能。这是我们不希望看到的。因此最好使用 = default来修饰默认构造函数。

实例3:

C++开发中,我们经常需要控制某些函数的生成。在C++11之前,我们经常的普遍做法是将其声明为类的 private 成员函数,这样若在类外这些这些函数的操作时候,编译器便会报错,从而达到效果。 如例1:

#include<iostream>
using namespace std;
class Student
{
public:Student() = default;Student(const int a,const int b):m_a(a),m_b(b) { }
​
int getA()const{return m_a;}
int getB()const{return m_b;}
​
private:Student(const Student& );Student& operator =(const Student& );
​
private:int m_a;int m_b;
};
​
int main(int argc,char **argv)
{
Student stu(1,2);
cout<<stu.getA()<<endl; //1
cout<<stu.getB()<<endl; //2
​
//Student stu1(stu);
//报错:Student.cpp:26:5: error: ‘Student::Student(const Student&)’ is private
​
//Student stu1(3,4);
//stu1 = stu;
//报错:Student.cpp:27:14: error: ‘Student& Student::operator=(const Student&)’ is private
​
std::cout<<is_pod<Student>::value<<std::endl;  //
return 0;
}

例1代码编译报错,因为在类中,我们将Student的拷贝构造函数和拷贝赋值函数都声明为了 private 属性,因此,当在类外使用拷贝构造和拷贝赋值操作值,编译器会报错。虽然能够达到效果,但是不够直观和简洁。对于追求高效以及简洁来说,这样做有2个问题:    

 (1)不是最简化;

 (2)对于友元支持不友好. 更为简洁直观的方法是使用:=delete

#include<iostream>
using namespace std;
class Student
{
public:Student() = default;Student(const int a,const int b):m_a(a),m_b(b){
​}
​
int getA()const{return m_a;}
int getB()const{return m_b;}
​Student(const Student& ) = delete;Student& operator =(const Student& ) = delete;
​
private:
int m_a;
int m_b;
};
​
int main(int argc,char **argv)
{
Student stu(1,2);
cout<<stu.getA()<<endl; //1
cout<<stu.getB()<<endl; //2
​
//Student stu1(stu);
//报错:Student.cpp:39:21: error: use of deleted function ‘Student::Student(const Student&)’
​
//Student(const Student& );
​
//Student stu1(3,4);
//stu1 = stu;
//报错:SStudent.cpp:44:10: error: use of deleted function ‘Student& Student::operator=(const Student&)’
​
std::cout<<is_pod<Student>::value<<std::endl;  //
return 0;
}

注:若缺省版本被删除了,重载该函数是非法的.

参考

  1. https://guoqiang.blog.csdn.net/article/details/115452089
  2. https://en.cppreference.com/w/cpp/language/member_functions#Special_member_functions
  3. https://en.cppreference.com/w/cpp/language/function#Deleted_functions

【C++】C/C++ 中default/delete特性相关推荐

  1. c++中delete对象后 调用成员函数_C++类的特殊成员函数及default/delete特性

    本文包含以下内容 1. C++的四类特殊成员函数介绍,重点介绍拷贝构造函数和拷贝复制运算符 2. C++11中的default/delete特性 本文内容侧重个人理解,深入理解其原理推荐https:/ ...

  2. iOS7 中的新特性

    iOS7 中的新特性 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳 ...

  3. iOS 各版本中的新特性(What's New in iOS)- 目录翻译完成

    iOS 各版本中的新特性(What's New in iOS) 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致&q ...

  4. 第一次来,试发一帖!--ASP.NET 2.0 中的SqlCacheDependency特性

    ASP.NET中的Page Cache是个很有用的东东,只要简单的在页面上方加上一个OutputCache标签,就可以让页面在制定的Duration内直接把自动保存在缓存中的页面内容输出,而不需要让A ...

  5. Spring 2.5:Spring MVC中的新特性

    转载说明:infoQ就是牛人多,看人家去年就把Spring2.5注视驱动的MVC写出来了,还是这么详细,我真是自叹不如,今天偶尔看到这篇文章非常认真的拜读了2遍,简直是茅厕顿开啊....\(^o^)/ ...

  6. angular routerlink传递参数_[翻译]在 Angular 中使用 async-await 特性

    在 Angular 中使用 async-await 特性 原文链接: https://medium.com/@balramchavan/using-async-await-feature-in-ang ...

  7. ASP.NET 2.0 中的SqlCacheDependency特性

    ASP.NET中的Page Cache是个很有用的东东,只要简单的在页面上方加上一个OutputCache标签,就可以让页面在制定的Duration内直接把自动保存在缓存中的页面内容输出,而不需要让A ...

  8. java中default关键字

    default关键字介绍 default是在java8中引入的关键字,也可称为Virtual extension methods--虚拟扩展方法. 它是指,在接口内部包含了一些默认的方法实现(也就是接 ...

  9. JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)

    文章目录 1. 新语法结构 1.1 Java的REPL工具: jShell命令 1.2 异常处理之try-catch资源关闭 1.3 局部变量类型推断 1.4 instanceof的模式匹配 1.5 ...

最新文章

  1. 自然语言处理NLP之主题模型、LDA(Latent Dirichlet Allocation)、语义分析、词义消歧、词语相似度
  2. android内置picker控件,android中控件DatePicker控件-Fun言
  3. 可长点心吧-sort
  4. 一秒点击手机屏幕次数_手机电池不耐用,都怪这些充电坏毛病
  5. 反射获取类的几种方法
  6. 显示和隐藏菜单栏(两种方式div、table)
  7. 模型与高性能服务器结合,Epoll模型的高性能服务器丢失数据问题解决
  8. SPSS 卡方检验(图文+数据集)【SPSS 019期】
  9. PHP的CI框架接入redis
  10. python打开pcap文件_python 抓包保存为pcap文件并解析的实例
  11. 计算机仿真软件在医疗应用,医学虚拟仿真应用介绍
  12. [转载]快速实现微信扫码关注公众号/用户注册并登陆
  13. 【一步步学OpenGL 27】 -《公告牌技术与几何着色器》
  14. python可以查ip地址吗_python实现查询IP地址所在地
  15. c#字符串的格式化输出
  16. 【LeetCode击败99%+】猜数字大小
  17. python编程查看gpu显存使用
  18. 【小技术】数据库显示“远程过程调用失败”怎么办?
  19. 用 canvas 做一个 DVD 待机动画
  20. 用计算机怎么算极限,2019计算机考研数学必考知识点:极限的计算

热门文章

  1. Ubuntu环境下使用gnuplot由数据表绘制曲线图
  2. 关闭tomact被占用的进程
  3. python-pcl GPU、输入输出模块教程翻译
  4. 力扣(LeetCode)刷题,简单+中等题(第35期)
  5. opencv获取图像像素值的坑
  6. Linux那些事儿 之 戏说USB(32)驱动的生命线(四)
  7. CSS3重新定义input中呆若木鸡的默认复选框CheckBox和单选框Radio样式
  8. 数据结构与算法常用名词术语整理
  9. Rocksdb 的优秀代码(一) -- 工业级分桶算法实现分位数p50,p99,p9999
  10. TCP/IP 协议栈4层结构及3次握手4次挥手