首页 > 编程语言 >C++ 类成员函数全家桶

C++ 类成员函数全家桶

时间:2023-09-19 21:32:16浏览次数:27  
标签:初始化 函数 对象 全家 C++ 运算符 拷贝 构造函数 赋值

RAII

Resource Acquisition Is Initialization,资源获取即初始化

这是一种解决资源管理问题的方法,将资源的有效期与持有资源的对象的生命期严格绑定,由对象的构造函数完成资源的分配,由析构函数完成资源的释放 C++借助构造函数和析构函数,解决了传统的 malloc & freenew & delete 管理资源方法无法有效应对复杂资源管理场景的问题

对于资源管理需求,无GC机制的C++语言提供了基于析构函数的RAII方法和智能指针,有GC机制的Java语言提供了垃圾回收机制(Garbage Collection,GC).需要注意的是,虽然它们都能避免发生内存泄漏问题,但实现原理并不相同.

首先,从语言的设计角度来说,C++的设计应用场景要求其对代码的执行过程做到可控,RAII方法仍然需要手动释放资源,而GC机制并不需要开发者手动释放资源(这也是RAII方法和GC机制的一个显著区别), 即RAII并没有剥夺开发者手动管理资源的权限,它只是通过构造和析构函数提供了一种安全管理资源的方法,开发者对于执行过程是具有控制权的.而GC机制把资源的回收过程交由JVM负责,开发者无权控制.

关于可控性,还体现在析构函数和垃圾回收发生的时机上,在C++中通过{}来界定作用范围,当超出作用范围后即会调用对象的析构函数,即析构函数的调用时间是可预知的(适用于对时序有严格要求的场景),而垃圾回收机制的发生时间取决于JVM的资源管理策略,是开发者无权控制的.

前言

资源管理和类的控制实际是两个分离的过程,RAII方法则是利用类的成员函数解决资源管理的方法之一

普通构造函数

无参

  1. 自定义 1.1 普通形式 1.2 使用初始化表达式

  2. 默认 POD陷阱(哪些类型不会自动初始化为0,大括号指定初始化值(0初始化方式))或等号指定初始化值(无法采用小括号指定初始化值) 支持单纯的大括号初始化,等号和大括号同时使用的初始化方式

当自定义构造函数后,默认无参构造函数就不存在了,可通过default生成默认的无参构造函数

单参数

此时单参数的构造函数,实现了从一种类型隐式地转换为类类型,因此此时的构造函数也称为转换构造函数,需要注意的是C++ 类型转换中提到的编译器只支持一步隐式类型转换规则.

如果想禁止这种隐式类型转换,可以使用explicit关键字修饰单参数的构造函数,此时该构造函数只能通过直接初始化使用,编译器也不会在隐式类型转换过程中调用该构造函数

额外需要注意的是explicit关键字只能出现在类内部构造函数声明处 explicit的中文释义是明确的,也就是说其修饰的函数应当显式调用,如果不加此修饰符,隐式类型转换过程会在背后执行这一函数,不能一眼看出

多参数

  1. 普通形式
  2. 使用初始化表达式
class ConstRef {
public:
	ConstRef(int ii);
private:
	int i;
	const int ci;
	int &ri;
};

ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) {}

What circumstances must use the initialization expression ?

  1. const
  2. quote
  3. class type which doesn't provide default constructor function

explicit禁止隐式转换(我自己说的,具体作用要查一下)

拷贝构造函数(copy constructor)

  • 定义了用同类型的另一个对象初始化本对象时的操作

首先涉及到的就是拷贝的概念。拷贝分为深拷贝和浅拷贝。 浅拷贝是指把一个对象所在内存中的数据以二进制形式复制到另一个对象中,对于包含指针类型成员变量的类而言,浅拷贝意味着源对象和目标对象的指针指向相同的内存空间,即改变其中一者的值,另一者的值也会发生改变。在此情况下,深拷贝在复制对象本身同时,会把对象拥有的数据一同复制,源对象和目标对象是两个独立的对象。

拷贝构造函数特征

  • 第一个参数是自身类型引用
  • 额外参数均存在默认值
  • 存在几种情况需要被隐式使用,因此通常不设置为explicit
  • 可通过delete禁用拷贝构造函数

<details> <summary>拷贝构造函数示例代码</summary>

class Foo {
public:
  Foo();
  Foo(const Foo&);
};

</details>

问: 为什么拷贝构造函数的参数需要是引用类型 答: 拷贝构造函数用于初始化非引用类类型参数,为了调用拷贝构造函数,需要拷贝它的实参,如果参数不是引用类型,则为了拷贝实参,仍然需要调用拷贝构造函数,进入无限循环[^why-need-copy-constructor].(目前并没有完全理解)

[^why-need-copy-constructor]: C++ Primer 中文版(第 5 版)-P442-参数和返回值

拷贝赋值运算符(copy-assignment operator)

  • 定义了将一个对象赋值同类型的另一个对象时的操作

拷贝赋值运算符并不是一个新的内容,其遵循运算符重载的规则

可以通过delete禁止对象赋值

struct NoCopy {
  NoCopy& operator=(const NoCopy&) = delete; // 禁止赋值
};

为何需要自定义拷贝赋值运算符? 一个类类型中包含多个成员属性,如果在进行对象赋值时,仅需要修改部分属性值,则需要自定义实现拷贝赋值运算符

需要了解拷贝构造函数和拷贝赋值运算符作用上的区别,拷贝构造是在对象还没有初始化时,拷贝另一个对象用于初始化当前对象,拷贝赋值是当前对象已经初始化,用另一个对象修改当前对象.

移动构造函数(move constructor)

  • 定义了用同类型的另一个对象初始化本对象时的操作
  • C++11 引入

问: 为何引入移动语义? 答: 1. 多数情况下对象拷贝操作后就被销毁了,此时移动要比拷贝+删除更能提高性能 2. 拷贝意味着存在多份,由于某些资源不允许共享,因此不能拷贝但是可以移动

即使有以上两点引入移动语义的原因,但有时仍会产生一些疑问,例如:想不到移动操作的应用场景有哪些,移动操作的效果是把一个对象的数据移动到另一个对象中,并且让源对象数据失效,数据在源对象中正常存在为什么一定要移动到一个新的对象当中呢,并且还让源对象失效,既然这样为什么不直接使用源对象。 解释:关于这一点疑惑,unique_ptr就是一个用于解释很好的例子,参数传递是一个会发生对象转移的场景,而unique_ptr恰好不支持出现多份数据,只能保留一份,在没有移动语义之前,这种场景极容易发生错误,而移动语义则在保证始终只保留一份数据的情况下,做到数据的转移。

在理解移动语义之前,需要理解C++ 左值、右值、右值引用的概念,主要是std::move函数的概念

在能够确定移动构造函数不会触发异常时,要添加noexcept标识符 原因:p500 避免标准库容器在不清楚情况的状态下做出一些损耗性能的额外工作 不过这种规则似乎已经涉及到优化的问题了,而我们目前还没有达到这一步

移动赋值运算符(move-assignment operator)

  • 定义了将一个对象赋值同类型的另一个对象时的操作
  • C++11 引入

解构(析构)函数(destructor)

定义了对象销毁时的操作

解构(析构)函数特征

  • 无参数,故不能被重载
  • 一个类只存在一个析构函数

合成操作

所谓"合成"是指在开发者没有明确给出实现的情况下,由编译器负责给出默认的实现

  1. 合成默认构造函数

  2. 合成拷贝操作(拷贝构造函数和拷贝赋值运算符)的条件 未声明自定义拷贝构造和拷贝赋值时,编译器会合成这些操作

  3. 合成移动操作(移动构造函数和移动赋值运算符)的条件

  • 类没有自定义的拷贝控制函数和拷贝赋值运算符
  • 类的每个非static数据成员均可移动

三/五法则

  • 三法则是指负责拷贝控制操作的拷贝构造函数拷贝赋值运算符析构函数三者的使用规则
  • 五法则是指在三法则基础上,添加了C++11中新增的移动语义所带来的移动构造函数移动赋值运算符的使用规则
  1. 如果一个类需要自定义析构函数,那么它也需要自定义拷贝构造函数和拷贝赋值运算符 解释: 需要自定义析构函数,说明资源管理是需要自定义的,那么资源分配过程显然也需要自定义,自然就需要自定义拷贝构造和赋值;

  2. 需要拷贝操作的类也需要赋值操作,需要赋值操作的类也需要拷贝操作 拷贝构造和拷贝赋值是近似的操作,二者几乎是同时存在的,但是需要构造和赋值并不一定需要析构

标签:初始化,函数,对象,全家,C++,运算符,拷贝,构造函数,赋值
From: https://blog.51cto.com/u_14882565/7529317

相关文章

  • Python实现排序的方式有:内置函数sort()和sorted()以及lambda函数
    排序是计算机编程中经常需要用到的操作,它将一组数据按照规则重新排列,以便更好地处理数据。在Python中,有多种方法可以对数组进行排序,本文将从多个方面进行介绍。一、Python中的排序方法Python中内置了多个排序算法,包括冒泡排序、插入排序、选择排序、快速排序等。使用内置的sort(......
  • 深入解析 MySQL 中的字符串处理函数:RIGHT()、LEFT() 和 CHAR_LENGTH
    在MySQL数据库中,字符串处理是一个常见的任务,特别是当你需要从字符串中提取特定部分或者计算字符串的长度时。我们在之前的博文中已经介绍过SUBSTRING_INDEX()、SUBSTRING_INDEX()、SUBSTRING_INDEX(),感兴趣的朋友了可以翻一下我们之前的博文;在本文中,我们将深入探讨三个重要的字......
  • WebAssembly实践指南——C++和Rust通过wasmtime实现相互调用实例
    C++和Rust通过wasmtime实现相互调用实例1wasmtime介绍wasmtime是一个可以运行WebAssembly代码的运行时环境。WebAssembly是一种可移植的二进制指令集格式,其本身与平台无关,类似于Java的class文件字节码。WebAssembly本来的设计初衷是想让浏览器可以运行C语言这种编译型语言的......
  • C++-类和对象(5)
    今天,继续和大家分享与类和对象相关的知识,本次的内容包含日期类的实现,const成员,static成员及友元函数等方面。继上篇文章,我们说到了日期类前置++和后置++的实现。现在,我们从前置--和后置--接着往下,完成我们日期类的实现。日期类的实现日期类前置--和后置--的实现在实现前置--和后置-......
  • 金拱门全家桶
    #defineintlonglong#defineN2000005usingnamespacestd;namespacePolynomial{ constintmod=998244353,g=3,ig=332748118,B=25000; inlinevoidadd(int&x,inty){(x+=y)>=mod?x-=mod:x;} intpwg[B+1],PWG[B+1],pwig[B+1],PWIG[B+1],inv[N],lg[N],pw[N]......
  • 无涯教程-JavaScript - SUMIF函数
    描述您可以使用SUMIF函数对满足指定条件的范围内的值求和。语法SUMIF(range,criteria,[sum_range])争论Argument描述Required/Optionalrange您要通过条件判断的单元格范围。每个范围中的单元格必须是数字或包含数字的名称,数组或引用。空白和文本值将被忽略。......
  • iOS开发Swift-回调函数
    回调函数:回调函数是一种将函数作为参数传递给另一函数的策略。当特定事件或条件发生时,传递的函数(即回调函数)将被调用。这种机制允许在事件发生时执行自定义的代码,因此它是异步编程的重要组成部分。在Swift中,可以使用闭包(closure)或函数作为回调函数。假设你有一个函数叫做greet(......
  • Python第六章函数(1)普通函数
    1.基本定义:def函数名(参数列表):函数体2.可以选择性用文档字符串存放函数说明,可以用help()和“函数名._doc_”查看函数注释。3.函数标注4.return语句:不带表达式的return返回none。5.全局变量和局部变量:x=200deffun():x=100则函数内......
  • 为什么 Python 代码在函数中运行得更快?
    哈喽大家好,我是咸鱼当谈到编程效率和性能优化时,Python常常被调侃为“慢如蜗牛”有趣的是,Python代码在函数中运行往往比在全局范围内运行要快得多小伙伴们可能会有这个疑问:为什么在函数中运行的Python代码速度更快?今天这篇文章将会解答大家心中的疑惑原文链接:https://stac......
  • 无涯教程-JavaScript - SUM函数
    描述SUM函数可添加值。语法SUM(number1,[number2]...)争论Argument描述Required/Optionalnumber1Thefirstnumberyouwanttoadd.Thenumbercanbeavalue,acellreference,oracellrange.Requirednumber2,…Youcanspecifyupto255additionaln......