C++中类的拷贝控制

C++中类的拷贝控制

转自:https://www.cnblogs.com/ronny/p/3734110.html

1,什么是类的拷贝控制

当我们定义一个类的时候,为了让我们定义的类类型像内置类型(char,int,double等)一样好用,我们通常需要考下面几件事:

Q1:用这个类的对象去初始化另一个同类型的对象。

Q2:将这个类的对象赋值给另一个同类型的对象。

Q3:让这个类的对象有生命周期,比如局部对象在代码部结束的时候,需要销毁这个对象。

因此C++就定义了5种拷贝控制操作,其中2个移动操作是C++11标准新加入的特性:

拷贝构造函数(copy constructor)

移动构造函数(move constructor)

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

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

析构函数 (destructor)

前两个构造函数发生在Q1时,中间两个赋值运算符发生在Q2时,而析构函数则负责类对象的销毁。

但是对初学者来说,既是福音也是灾难的是,如果我们没有在定义的类里面定义这些控制操作符,编译器会自动的为我们合成一个版本。这有时候看起来是好事,但是编译器不是万能的,它的行为在很多时候并不是我们想要的。

所以,在实现拷贝控制操作中,最困难的地方是认识到什么时候需要定义这些操作

2,拷贝构造函数

拷贝构造函数是构造函数之一,它的参数是自身类类型的引用,且如果有其他参数,则任何额外的参数都有默认值。

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

我们从上面代码中可以注意到几个问题:

1,我们把形参定义为const类型,虽然我们也可以定义非const的形参,但是这样做基本上没有意义的,因为函数的功能只涉及到成员的复制操作。

2,形参是本身类类型的引用,而且必须是引用类型。为什么呢?

我们知道函数实参与形参之间的值传递,是通过拷贝完成的。那么当我们将该类的对象传递给一个函数的形参时,会调用该类的拷贝构造函数,而拷贝构造函数本身也是一个函数,因为是值传递而不是引用,在调用它的时候也需要调用类的拷贝构造函数(它自身),这样无限循环下去,无法完成。

3,拷贝构造函数通过不是explict的。

如果我们没有定义拷贝构造函数,编译器会为我们定义一个,这个函数会从给定的对象中依次将每个非static成员拷贝到正在创建的对象中。成员自身的类型决定了它是如何被拷贝的:类类型的成员,会使用其拷贝构造函数来拷贝;内置类型则直接拷贝;数组成员会逐元素地拷贝

区分直接初始化与拷贝初始化:

string name("name_str");        //直接初始化 
string name = string("name_str");    // 拷贝初始化 
string name = "name_str";        // 拷贝初始化

直接初始化是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数;当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换(第三行代码隐藏了一个C风格字符串转换为string类型)

3,拷贝赋值运算符

拷贝赋值运算符是一个对赋值运算符的重载函数,它返回左侧运算对象的引用。

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

与拷贝构造函数一样,如果没有给类定义拷贝赋值运算符,编译器将为它合成一个。

4,析构函数

析构函数是由波浪线接类名构成,它没有返回值,也不接受参数。因为没有参数,所以它不存在重载函数,也就是说一个类只有一个析构函数。

析构函数做的事情与构造函数相反,那么我们先回忆一个构造函数都做了哪些事:

1,按成员定义的顺序创建每个成员。

2,根据成员初始化列表初始化每个成员。

3,执行构造函数函数体。

而析构函数中不存在类似构造函数中初始化列表的东西来控制成员如何销毁,析构部分是隐式的。成员如何销毁依赖于成员自身的类型,如果是类类型则调用本身的析构函数,如果是内置类型则会自动销毁。而如果是一个指针,则需要手动的释放指针指向的空间。与普通指针不同的是,智能指针是一个类,它有自己的析构函数。

那么什么时候会调用析构函数呢?在对象销毁的时候:

  • 变量在离开其作用域时被销毁;
  • 当一个对象被销毁时,其成员被销毁。
  • 容器被销毁时,成员被销毁。
  • 对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁。
  • 对于临时对象,当创建它的赛事表达式结束时被销毁。

值得注意的析构函数是自动运行的。析构函数的函数体并不直接销毁成员,成员是在析构函数体之后隐含的析构阶段中被销毁的。在整个对象销毁过程中,析构函数体是作为成员销毁步骤之外的另一部分而进行的。

5,定义拷贝控制操作的原则

在第1点里有提过,在定义类的时候处理拷贝控制最困难的在于什么时候需要自己定义,什么时候让编译器自己合成。

那么我们可以有下面2点原则:

如果一个类需要定义析构函数,那么几乎可以肯定它也需要一个拷贝构造函数和一个拷贝赋值函数,反过来不一定成立。

如果一个类需要一个拷贝构造函数,几乎可以肯定它也需要一个拷贝赋值函数,反之亦然。

为什么析构函数与拷贝构造函数与赋值函数关系这么紧密呢,或者说为什么我们在讨论拷贝控制(5种)的时候要把析构函数一起放进来呢?

首先,我们思考什么时候我们一定要自己来定义析构函数,比如:类里面有动态分配内存。

class HasPtr 
{ 
public: HasPtr(const string&s = string()) :ps(new string(s), i(0)){} ~HasPtr(){ delete ps; } 
private: int i; string* ps; 
};

我们知道如果是编译器自动合成的析构函数,则不会去delete指针变量的,所以ps指向的内存将无法释放,所以一个主动定义的析构函数是需要的。那么如果没有给这个类定义拷贝构造函数和拷贝赋值函数,将会怎么样?

编译器自动合成的版本,将简单的拷贝指针成员,这意味着多个HasPtr对象可能指向相同的内存。

HasPtr p("some values"); 
f(p);        // 当f结束时,p.ps指向的内存被释放 
HasPtr q(p);// 现在p和q都指向无效内存

6,使用=default和=delete

我们可以使用=default来显式地要求编译器生成合成的版本。合成的函数将隐式地声明为内联的,如果我们不希望合成的成员是内联的,应该只对成员的类外定义使用=default。

有的时候我们定义的某些类不需要拷贝构造函数和拷贝赋值运算符,比如iostream类就阻止拷贝,以避免多个对象写入或读取相同的IO缓冲。

新的标准里,我们可以在拷贝构造函数和拷贝赋值运算符函数的参数列表后面加上=delete用来指出我们希望将它定义为删除的,这样的函数称为删除函数。

class NoCopy 
{ NoCopy() = default;    // 使用合成的默认构造函数 NoCopy(const NoCopy&) = delete;        // 删除拷贝 NoCopy& operator=(const NoCopy&) = delete;    // 删除赋值 ~NoCopy() = default;    // 使用合成的析构函数 
};

注意:析构函数不能是删除的成员,因为这样的类是无法销毁的。

如果一个类有const成员或者有引用成员,则这个类合成拷贝赋值运算符是被定义为删除的。

在新的标准出来之前,类是通过将其拷贝构造函数的拷贝赋值运算符声明为private来阻止拷贝,而且为了防止成员被友元或其他成员访问,会对这些成员函数只声明,但不定义。

7,右值引用

所谓的右值引用就是必须绑定在右值上的引用,我们可以通过&&来获得右值引用,右值引用一个很重要的性质是只能绑定到一个将要销毁的对象,所以我们可以自由地将一个右值引用的资源“移动”到另一个对象中。

我们可以将一个右值引用绑定到表达式上,但不能将右值引用绑定到一个左值上:

int i = 42; 
int &r = i;        // 正确:r引用i 
int &&rr = i;    // 错误:不能将一个右值引用绑定到一个左值上 
int &r2 = i * 42;    // i*42是一具右值 
const int& r3 = i * 42;    // 可以将一个const的引用绑定到一个右值上 
int && rr2 = i * 42;    // 正确:将rr2绑定到乘法结果上

总体来说:左值有持久的状态,而右值要么是字面常量,要么是表达式求值过程中创建的临时对象。

从而我们得知,关于右值引用:1)所引用的对象将要销毁;2)该对象没有其他用户。

标准库提供了一个std::move函数,让我们可以获得左值上的右值引用:

int  &&r3 = std::move(rr1); // rr1是一个变量

move调用告诉编译器:我们有一个左值,但是我们希望像一个右值一个处理它。在上面的代码后,要么销毁rr1,要么对rr1进行赋值,否则我们不能使用rr1。

另外一点值得注意的是,我们使用std::move而不是move,即使我们提供了using声明。

8,移动构造函数和移动赋值运算符

与拷贝一样,移动操作同样发生在我们一个类的对象去初始化或赋值同一个类类型的对象时,但是与拷贝不同的是,对象的内容实际上从源对象移动到了目标对象,而源对象丢失了内容。移动操作一般只发生在当这个源对象是一个uname的对象的时候。

一个uname object意思是一个临时对象,还没有被赋予一个名字,例如一个返回该类型的函数返回值或者一个类型转换操作返回的对象。

MyClass fn();            // function returning a MyClass object
MyClass foo;             // default constructor
MyClass bar = foo;       // copy constructor
MyClass baz = fn();      // move constructor
foo = bar;               // copy assignment
baz = MyClass();         // move assignment 

上面的代码中由fn()返回的对象和由MyClass构造出来的对象都是unnamed,用这样的对象给MyClass赋值或初始化时,并不需要拷贝,因为源对象只有很短的生命周期。

移动构造函数与移动赋值函数的定义形式上与拷贝操作一样,只是将拷贝函数的形参的引用换成右值引用。

MyClass (MyClass&&);             // move-constructor
MyClass& operator= (MyClass&&);  // move-assignment

移动操作对那些需要管理存储空间的类是非常有用的,比如我们下面定义的这个类

// move constructor/assignment
#include <iostream>
#include <string>
using namespace std;class Example6 {string* ptr;public:Example6 (const string& str) : ptr(new string(str)) {}~Example6 () {delete ptr;}// move constructorExample6 (Example6&& x) : ptr(x.ptr) {x.ptr=nullptr;}// move assignmentExample6& operator= (Example6&& x) {delete ptr; ptr = x.ptr;x.ptr=nullptr;return *this;}// access content:const string& content() const {return *ptr;}// addition:Example6 operator+(const Example6& rhs) {return Example6(content()+rhs.content());}
};int main () {Example6 foo ("Exam");Example6 bar = Example6("ple");   // move-constructionfoo = foo + bar;                  // move-assignmentcout << "foo's content: " << foo.content() << '\n';return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/532576.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

centos7ubuntu搭建Vulhub靶场(推荐Ubuntu)

这里写目录标题一.前言总结二.成功操作&#xff1a;三.出现报错&#xff1a;四.vulhub使用正文&#xff1a;一.前言总结二.成功操作&#xff1a;三.出现报错&#xff1a;四.vulhub使用看完点赞关注不迷路!!!! 后续继续更新优质安全内容!!!!!一.前言总结 二.成功操作&#xff1…

使用 PyTorch 数据读取,JAX 框架来训练一个简单的神经网络

使用 PyTorch 数据读取&#xff0c;JAX 框架来训练一个简单的神经网络 本文例程部分主要参考官方文档。 JAX简介 JAX 的前身是 Autograd &#xff0c;也就是说 JAX 是 Autograd 升级版本&#xff0c;JAX 可以对 Python 和 NumPy 程序进行自动微分。可以通过 Python的大量特征…

Yapi Mock 远程代码执行漏洞

跟风一波复现Yapi 漏洞描述&#xff1a; YApi接口管理平台远程代码执行0day漏洞&#xff0c;攻击者可通过平台注册用户添加接口&#xff0c;设置mock脚本从而执行任意代码。鉴于该漏洞目前处于0day漏洞利用状态&#xff0c;强烈建议客户尽快采取缓解措施以避免受此漏洞影响 …

C++ ACM模式输入输出

C ACM模式输入输出 以下我们都以求和作为题目要求&#xff0c;来看一下各种输入输出应该怎么写。 1 只有一个或几个输入 输入样例&#xff1a; 3 5 7输入输出模板&#xff1a; int main() {int a, b, c;// 接收有限个输入cin >> a >> b >> c;// 输出结果…

CVE-2017-10271 WebLogic XMLDecoder反序列化漏洞

漏洞产生原因&#xff1a; CVE-2017-10271漏洞产生的原因大致是Weblogic的WLS Security组件对外提供webservice服务&#xff0c;其中使用了XMLDecoder来解析用户传入的XML数据&#xff0c;在解析的过程中出现反序列化漏洞&#xff0c;导致可执行任意命令。攻击者发送精心构造的…

树莓派摄像头 C++ OpenCV YoloV3 实现实时目标检测

树莓派摄像头 C OpenCV YoloV3 实现实时目标检测 本文将实现树莓派摄像头 C OpenCV YoloV3 实现实时目标检测&#xff0c;我们会先实现树莓派对视频文件的逐帧检测来验证算法流程&#xff0c;成功后&#xff0c;再接入摄像头进行实时目标检测。 先声明一下笔者的主要软硬件配…

【实战】记录一次服务器挖矿病毒处理

信息收集及kill&#xff1a; 查看监控显示长期CPU利用率超高&#xff0c;怀疑中了病毒 top 命令查看进程资源占用&#xff1a; netstat -lntupa 命令查看有无ip进行发包 netstat -antp 然而并没有找到对应的进程名 查看java进程和solr进程 ps aux &#xff1a;查看所有进程…

ag 搜索工具参数详解

ag 搜索工具参数详解 Ag 是类似ack&#xff0c; grep的工具&#xff0c;它来在文件中搜索相应关键字。 官方列出了几点选择它的理由&#xff1a; 它比ack还要快 &#xff08;和grep不在一个数量级上&#xff09;它会忽略.gitignore和.hgignore中的匹配文件如果有你想忽略的文…

CVE-2013-4547 文件名逻辑漏洞

搭建环境&#xff0c;访问 8080 端口 漏洞说明&#xff1a; Nginx&#xff1a; Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;在BSD-like 协议下发行。其特点是占有内存少&#xff0c;并发能力强&#xf…

CMake指令入门 ——以构建OpenCV项目为例

CMake指令入门 ——以构建OpenCV项目为例 转自&#xff1a;https://blog.csdn.net/sandalphon4869/article/details/100589747 一、安装 sudo apt-get install cmake安装好后&#xff0c;输入 cmake -version如果出现了cmake的版本显示&#xff0c;那么说明安装成功 二、c…

CVE-2017-7529Nginx越界读取缓存漏洞POC

漏洞影响 低危&#xff0c;造成信息泄露&#xff0c;暴露真实ip等 实验内容 漏洞原理 通过查看patch确定问题是由于对http header中range域处理不当造成&#xff0c;焦点在ngx_http_range_parse 函数中的循环&#xff1a; HTTP头部range域的内容大约为Range: bytes4096-81…

Linux命令行性能监控工具大全

Linux命令行性能监控工具大全 作者&#xff1a;Arnold Lu 原文&#xff1a;https://www.cnblogs.com/arnoldlu/p/9462221.html 关键词&#xff1a;top、perf、sar、ksar、mpstat、uptime、vmstat、pidstat、time、cpustat、munin、htop、glances、atop、nmon、pcp-gui、collect…

Weblogic12c T3 协议安全漏洞分析【CVE-2020-14645 CVE-2020-2883 CVE-2020-14645】

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 关注公众号&#xff1a;b1gpig信息安全&#xff0c;文章推送不错过 ## 前言 WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAV…

Getshell总结

按方式分类&#xff1a; 0x01注入getshell&#xff1a; 0x02 上传 getwebshell 0x03 RCE getshell 0x04 包含getwebshell 0x05 漏洞组合拳getshell 0x06 系统层getcmdshell 0x07 钓鱼 getcmdshell 0x08 cms后台getshell 0x09 红队shell竞争分析 0x01注入getshell&#xff1a;…

编写可靠bash脚本的一些技巧

编写可靠bash脚本的一些技巧 原作者&#xff1a;腾讯技术工程 原文链接&#xff1a;https://zhuanlan.zhihu.com/p/123989641 写过很多 bash 脚本的人都知道&#xff0c;bash 的坑不是一般的多。 其实 bash 本身并不是一个很严谨的语言&#xff0c;但是很多时候也不得不用。以下…

python 到 poc

0x01 特殊函数 0x02 模块 0x03 小工具开发记录 特殊函数 # -*- coding:utf-8 -*- #内容见POC.demo; POC.demo2 ;def add(x,y):axyprint(a)add(3,5) print(------------引入lambad版本&#xff1a;) add lambda x,y : xy print(add(3,5)) #lambda函数,在lambda函数后面直接…

protobuf版本常见问题

protobuf版本常见问题 许多软件都依赖 google 的 protobuf&#xff0c;我们很有可能在安装多个软件时重复安装了多个版本的 protobuf&#xff0c;它们之间很可能出现冲突并导致在后续的工作中出现版本不匹配之类的错误。本文将讨论笔者在使用 protobuf 中遇到的一些问题&#…

CMake常用命令整理

CMake常用命令整理 转自&#xff1a;https://zhuanlan.zhihu.com/p/315768216 CMake 是什么我就不用再多说什么了&#xff0c;相信大家都有接触才会看一篇文章。对于不太熟悉的开发人员可以把这篇文章当个查找手册。 1.CMake语法 1.1 指定cmake的最小版本 cmake_minimum_r…

CVE-2021-41773 CVE-2021-42013 Apache HTTPd最新RCE漏洞复现 目录穿越漏洞

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; CVE-2021-41773漏洞描述&#xff1a; Apache HTTPd是Apache基金会开源的一款流行的HTTP服务器。2021年10月8日Apache HTTPd官方发布安全更新&#xff0c;披…

SSRF,以weblogic为案例

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 复习一下ssrf的原理及危害&#xff0c;并且以weblog的ssrf漏洞为案例 漏洞原理 SSRF(Server-side Request Forge, 服务端请求伪造) 通常用于控制web进而…