当前位置: 首页 > 编程日记 > 正文

【C++】C/C++ 中default/delete特性

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

相关文章:

Celery--任务调度利器

2019独角兽企业重金招聘Python工程师标准>>> Celery文档: http://docs.jinkan.org/docs/celery/getting-started/first-steps-with-celery.html 安装celery和celery-with-redis pip install Celery pip install celery-with-redis开始编写task.py # tasks.py import…

直方图变换——累计

#include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> int main() {// 图像获取及验证cv::Mat srcImage cv::imread("..\\images\\flower3.jpg");if( !srcImage.data ) return 1;//…

iOS开发经验总结,我的2019进阶之路!

4G改变了生活&#xff0c;抓住机会的人已经在这个社会有了立足之地&#xff0c;马上迎来5G的时代&#xff0c;你做好准备了吗&#xff01;对于即将迎来的5G时代&#xff0c;无疑会是音视频的又一个高潮&#xff01;那么作为程序员的我们&#xff0c;应该怎么样去迎接它呢~~ 改变…

【C++】C/C++ 中多态情形下的虚函数表查看方法

1.查看工具 找到VS2017命令提示符工具 选择“VS 2017的开发人员命令提示符” 点击该选项栏&#xff0c;弹出“VS 2017的开发人员命令提示符”窗口 cd 控制进入带查看类躲在的位置 使用命令&#xff1a;cl /d1 reportSingleClassLayoutXXX [filename]&#xff0c;XXX表示类名&…

PHP中session_register函数详解用法

语法: boolean session_register(string name);注册新的变量。返回值: 布尔值函数种类: 资料处理内容说明本函数在全域变量中增加一个变量到目前的 Session 之中。参数 name 即为欲加入的变量名。成功则返回 true 值。假如在头文件&#xff0c;开启session,即使用session_start…

Fedora 提出统一流程,弃用上千 Python 2 软件包更可控

开发四年只会写业务代码&#xff0c;分布式高并发都不会还做程序员&#xff1f; >>> Fedora 社区正在讨论弃用 Python 2 软件包的统一流程。 https://pythonclock.org Python 2 将于 2020 年 1 月 1 日正式退休&#xff0c;官方不再提供维护&#xff0c;当前倒计时不…

【C++】C++对象模型:对象内存布局详解(C#实例)

C对象模型&#xff1a;对象内存布局详解 0.前言 C对象的内存布局、虚表指针、虚基类指针解的探讨&#xff0c;参考。 1.何为C对象模型? 引用《深度探索C对象模型》这本书中的话&#xff1a; 有两个概念可以解释C对象模型&#xff1a; 语言中直接支持面向对象程序设计的部分…

Mybatis插件原理和PageHelper结合实战分页插件(七)

今天和大家分享下mybatis的一个分页插件PageHelper,在讲解PageHelper之前我们需要先了解下mybatis的插件原理。PageHelper的官方网站&#xff1a;https://github.com/pagehelper/Mybatis-PageHelper一、Plugin接口mybatis定义了一个插件接口org.apache.ibatis.plugin.Intercept…

直方图反向投影

#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> using namespace cv; using namespace std; int main() {// 加载源图像并验证cv::Mat srcImage cv::imread("..\\images\\hand1.jpg&quo…

Java异常处理12条军规

摘要&#xff1a; 简单实用的建议。 原文&#xff1a;Java异常处理12条军规公众号&#xff1a;Spring源码解析Fundebug经授权转载&#xff0c;版权归原作者所有。 在Java语言中&#xff0c;异常从使用方式上可以分为两大类&#xff1a; CheckedExceptionUncheckedException在Ja…

【Smart_Point】C/C++ 中独占指针unique_ptr

1. 独占指针unique_ptr 目录 1. 独占指针unique_ptr 1.1 unique_ptr含义 1.2 C11特性 1.3 C14特性 1.1 unique_ptr含义 unique_ptr 是 C 11 提供的用于防止内存泄漏的智能指针中的一种实现&#xff0c;独享被管理对象指针所有权的智能指针。unique_ptr对象包装一个原始指…

ORA-01940无法删除当前已连接用户

原文地址&#xff1a;ORA-01940无法删除当前已连接用户作者&#xff1a;17361887941)查看用户的连接状况 select username,sid,serial# from v$session ------------------------------------------ 如下结果&#xff1a; username sid serial# ------…

距离变换扫描实现

#include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> // 计算欧式距离 float calcEuclideanDistance(int x1, int y1, int x2, int y2) {return sqrt(…

『扩欧简单运用』

扩展欧几里得算法 顾名思义&#xff0c;扩欧就是扩展欧几里得算法&#xff0c;那么我们先来简单地回顾一下这个经典数论算法。 对于形如\(axbyc\)的不定方程&#xff0c;扩展欧几里得算法可以在\(O(log_2alog_2b)\)的时间内找到该方程的一组特解&#xff0c;或辅助\(gcd\)判断该…

【Smart_Point】C/C++ 中共享指针 shared_ptr

1. 共享指针 shared_ptr 目录 1. 共享指针 shared_ptr 1.1 共享指针解决的问题&#xff1f; 1.2 创建 shared_ptr 对象 1.3 分离关联的原始指针 1.4 自定义删除器 Deleter 1.5 shared_ptr 相对于普通指针的优缺点 1.6 创建 shared_ptr 时注意事项 1.1 共享指针解决的问…

对数变换的三种实现方法

#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> using namespace cv; // 对数变换方法1 cv::Mat logTransform1(cv::Mat srcImage, int c) {// 输…

eclipse编辑窗口不见了(打开左边的java、xml文件,中间不会显示代码)

转自&#xff1a;https://blog.csdn.net/u012062810/article/details/46729779?utm_sourceblogxgwz4 1. windows-->reset Perspective 窗口-->重置窗口布局 2. windows -> new windows 新窗口 当时手贱了一下&#xff0c;结果…

js的执行机制

javascript的运行机制一直困扰在我&#xff0c;对其的运行机制也是一知半解&#xff0c;在看了https://juejin.im/post/59e85eebf265da430d571f89#heading-10这篇文章后&#xff0c;有种茅塞顿开的感觉,下面是原文内容&#xff1a; 认识javascript javascript是一门单线程语言&…

【Smart_Point】unique_ptr中独占指针使用MakeFrame

1. DFrame使用方法 std::unique_ptr<deptrum::Frame> MakeFrame(deptrum::FrameType frame_type,int width,int height,int bytes_per_pixel,void* data,uint64_t timestamp,int index,float temperature) {std::unique_ptr<deptrum::Frame> frame{std::make_uniq…

最大熵阈值分割

#include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace std; using namespace cv; // 计算当前的位置的能量熵 float caculateCurrentE…

php登录注册

php 登录注册 注册代码&#xff1a;register.php <style type"text/css">form{width:300px;background-color:#EEE0E5;margin-left:300px;margin-top:30px;padding:30px;}button{margin-top:20px;} </style> <form method"post"> <la…

【C++】C/C++ 中的单例模式

目录 part 0&#xff1a;单例模式3种经典的实现方式 Meyers Singleton Meyers Singleton版本二 Lazy Singleton Eager Singleton Testing part 1&#xff1a;C之单例模式 动机 实现一[线程不安全版本] 实现二[线程安全&#xff0c;锁的代价过高] 锁机制 实现三[双检…

计算图像波峰点

#include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> #include <iostream> #include <vector> using namespace std; using namespace cv; // 计算图像的波峰…

锁的算法,隔离级别的问题

锁的算法 InnoDB存储引擎有3中行锁的算法设计&#xff0c;分别是&#xff1a; Record Lock&#xff1a;单个行记录上的锁。Gap Lock&#xff1a;间隙锁&#xff0c;锁定一个范围&#xff0c;但不包含记录本身。Next-Key Lock&#xff1a;Gap LockRecord Lock&#xff0c;锁定一…

好程序员分享24个canvas基础知识小结

好程序员分享24个canvas基础知识小结&#xff0c;非常全面详尽&#xff0c;推荐给大家。 现把canvas的知识点总结如下&#xff0c;以便随时查阅。 1、填充矩形 fillRect(x,y,width,height); 2、绘制矩形边框 strokeRect(x,y,width,height); 3、擦除矩形 clearRect(x,y,width,he…

【leetcode】二叉树与经典问题

文章目录笔记leetcode [114. 二叉树展开为链表](https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/)解法一: 后序遍历、递归leetcode [226. 翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/)思路与算法复杂度分析leetcode [剑指 Offer…

PHP PSR-1 基本代码规范(中文版)

基本代码规范 本篇规范制定了代码基本元素的相关标准&#xff0c;以确保共享的PHP代码间具有较高程度的技术互通性。 关键词 “必须”("MUST")、“一定不可/一定不能”("MUST NOT")、“需要”("REQUIRED")、“将会”("SHALL")、“不会…

最近邻插值实现:图像任意尺寸变换

#<opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace cv; using namespace std; // 实现最近邻插值图像缩放 cv::Mat nNeighbourInterpolatio…

软件测试-培训的套路-log3

最新的套路&#xff01;我是没了解过--下图中描述-log3 Dotest-董浩 但是我知道不管什么没有白吃的午餐而且还会给钱…如果真的有&#xff0c;请醒醒&#xff01; 当然话又回来&#xff0c;套路不套路&#xff0c;关键看你是否需要&#xff1b;你如果需要我觉得是帮你…不需要&…

ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

前几篇文章我们从dapm的数据结构入手&#xff0c;了解了代表音频控件的widget&#xff0c;代表连接路径的route以及用于连接两个widget的path。之前都是一些概念的讲解以及对数据结构中各个字段的说明&#xff0c;从本章开始&#xff0c;我们要从代码入手&#xff0c;分析dapm的…