C++中的移动语义

ops/2024/11/26 4:29:14/

一、移动语义
1.定义:

在C++ 中,移动语义是一种优化技术。

移动语义允许资源的“移动”而不是“拷贝”。在传统的 C++ 中,当一个对象被赋值或传递给函数时,通常会发生拷贝操作,这会导致性能下降,尤其是在处理大型对象时。移动语义通过引入右值引用和移动构造函数、移动赋值运算符,允许程序员将资源的所有权从一个对象转移到另一个对象,而不是进行深拷贝。

移动语义的作用:

2. 作用

  • 提高性能:移动语义主要用于避免对象中资源的不必要复制,特别是对于那些包含动态分配内存、文件句柄等资源的对象。它通过转移资源所有权来高效地处理对象的构造、赋值等操作,从而提升程序的运行效率。
  • 优化资源管理:在管理资源的类中,移动语义可以方便地将资源从一个对象转移到另一个对象,使得资源能够更灵活地被重新分配和利用。

下面是一个简单的 String 类,展示移动语义的实现:

#include <iostream>
#include <cstring>
class String {
private:
char* data;
size_t len;
public:
// 普通构造函数
String(const char* cstr = "") {
len = std::strlen(cstr);
data = new char[len + 1];
std::strcpy(data, cstr);
}
// 移动构造函数
String(String&& rhs) noexcept : data(rhs.data), len(rhs.len) {
rhs.data = nullptr;
rhs.len = 0;
}
// 移动赋值运算符
String& operator=(String&& rhs) noexcept {
if (this!= &rhs) {
delete[] data;
data = rhs.data;
len = rhs.len;
rhs.data = nullptr;
rhs.len = 0;
}
return this;
}
~String() {
delete[] data;
}
void print() const {
std::cout << data << std::endl;
}
};

在这个 String 类中:

1)移动构造函数 String(String&& rhs) 接收一个右值引用 rhs 。它将 rhs 对象中的 data 指针和 len 成员的值转移到新构造的对象中,然后将 rhs 对象的 data 指针设为 nullptr , len 设为 0 ,表示资源所有权已经转移。

2)移动赋值运算符 String& operator=(String&& rhs) 的作用类似。它先释放当前对象的资源( delete[] data ),然后将 rhs 对象的资源转移到当前对象,最后将 rhs 对象的资源相关成员重置。

String getString() {
String temp("Hello");
return temp;
}
int main() {
String str = getString();
str.print();
return 0;
}

在 main 函数中, getString 函数返回一个临时 String 对象。在没有移动语义的情况下,这个临时对象的资源( data 中的字符数组)会被复制到 str 对象中。但有了移动语义, str 对象通过移动构造函数直接接管了临时对象的资源,避免了一次可能很耗时的字符串复制操作。

二、右值引用
 

 右值引用的概念:

在C++中,右值引用是一种引用类型,用于绑定到右值。右值通常是临时对象或者即将销毁的值,如字面常量(如 3.14 、 'a' )、函数返回的临时对象等。右值引用使用  &&  来表示,它的出现主要是为了支持移动语义和完美转发。

1.函数返回值作为右值引用:

#include <iostream>
class MyClass {
public:
MyClass() {}
MyClass(const MyClass&) {
std::cout << "Copy constructor called." << std::endl;
}
MyClass(MyClass&&) noexcept {
std::cout << "Move constructor called." << std::endl;
}
};
MyClass getObject() {
MyClass obj;
return obj;
}
int main() {
MyClass newObj = getObject();
return 0;
}

在这个例子中, getObject 函数返回一个 MyClass 类型的临时对象。在 main 函数中, newObj 接收这个返回值。如果没有右值引用和移动语义,会调用复制构造函数来复制这个临时对象。但由于C++的编译器会识别这个临时返回值是右值,并且 MyClass 有移动构造函数,所以会调用移动构造函数,避免了不必要的复制。

2.字面常量作为右值引用

#include <iostream>
void printValue(int&& num) {
std::cout << "The value is: " << num << std::endl;
}
int main() {
printValue(10);
return 0;
}

这里定义了一个函数 printValue ,它的参数是一个右值引用 int&& num 。在 main 函数中,将字面常量 10 传递给 printValue 函数,因为 10 是右值,所以可以绑定到右值引用 num ,函数就可以使用这个右值进行操作。

使用右值引用实现移动语义

1. 实现步骤及示例代码(以自定义的资源管理类为例)

  • 步骤一:定义类和成员变量

首先,定义一个类,这个类包含需要移动的资源。假设我们创建一个简单的 String 类,它内部有一个字符指针来存储字符串内容。

class String {
private:
char* data;
size_t length;
public:
// 构造函数
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// 其他成员函数(如打印字符串)
void print() const {
std::cout << data << std::endl;
}
// 析构函数
~String() {
delete[] data;
}
// 移动构造函数(重点)
String(String&& other) noexcept {
// 步骤二:资源转移
data = other.data;
length = other.length;
// 步骤三:将源对象资源指针置空
other.data = nullptr;
other.length = 0;
}
// 移动赋值运算符(重点)
String& operator=(String&& other) noexcept {
if (this!= &other) {
// 释放当前对象资源
delete[] data;
// 资源转移
data = other.data;
length = other.length;
// 将源对象资源指针置空
other.data = nullptr;
other.length = 0;
}
return *this;
}
};
  • 步骤二:在移动构造函数和移动赋值运算符中进行资源转移

在移动构造函数 String(String&& other) 中,将 other 对象中的 data 指针和 length 属性赋值给新对象。这样,新对象就获得了资源的所有权。

  • 步骤三:将源对象资源指针置空或重置相关属性

在移动构造函数和移动赋值运算符完成资源转移后,需要将源对象( other )的相关资源指针置空(如 other.data = nullptr )或者重置相关属性(如 other.length = 0 ),以表明资源已经被转移,防止源对象的析构函数错误地释放已经转移的资源。

String getString() {
String temp("Hello");
return temp;
}
int main() {
String str = getString();
str.print();
return 0;
}

在 main 函数中, getString 函数返回一个临时对象 temp 。由于这个临时对象是右值,在初始化 str 对象时,会调用移动构造函数,将 temp 对象中的字符串资源转移到 str 对象中,而不是进行资源复制,提高了效率。


http://www.ppmy.cn/ops/136744.html

相关文章

数学建模学习(138):基于 Python 的 AdaBoost 分类模型

1. AdaBoost算法简介 AdaBoost(Adaptive Boosting)是一种经典的集成学习算法,由Yoav Freund和Robert Schapire提出。它通过迭代训练一系列的弱分类器,并将这些弱分类器组合成一个强分类器。算法的核心思想是:对于被错误分类的样本,在下一轮训练中增加其权重;对于正确分类…

MySQL底层概述—1.InnoDB内存结构

大纲 1.InnoDB引擎架构 2.Buffer Pool 3.Page管理机制之Page页分类 4.Page管理机制之Page页管理 5.Change Buffer 6.Log Buffer 1.InnoDB引擎架构 (1)InnoDB引擎架构图 (2)InnoDB内存结构 (1)InnoDB引擎架构图 下面是InnoDB引擎架构图&#xff0c;主要分为内存结构和磁…

Java语言编程,通过阿里云mongo数据库监控实现数据库的连接池优化

一、背景 线上程序连接mongos超时&#xff0c;mongo监控显示连接数已使用100%。 java程序报错信息&#xff1a; org.mongodb.driver.connection: Closed connection [connectionId{localValue:1480}] to 192.168.10.16:3717 because there was a socket exception raised by…

Java基础面试题02:简述什么是值传递和引用传递?

面试题&#xff1a;简述什么是值传递和引用传递&#xff1f; 什么是值传递&#xff1f; 值传递&#xff08;pass by value&#xff09;是指在调用函数时&#xff0c;把实际参数的值复制一份传递给函数。换句话说&#xff0c;函数内部对参数的任何修改&#xff0c;都不会影响到…

在Excel中处理不规范的日期格式数据并判断格式是否正确

有一个Excel表&#xff0c;录入的日期格式很混乱&#xff0c;有些看着差不多&#xff0c;但实际多一个空格少一个字符很难发现&#xff0c;希望的理想格式是 1980-01-01&#xff0c;10位&#xff0c;即&#xff1a;“YYYY-mm-dd”&#xff0c;实际上数据表中这样的格式都有 19…

Spring Boot教程之五:在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序

在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序 IntelliJ IDEA 是一个用 Java 编写的集成开发环境 (IDE)。它用于开发计算机软件。此 IDE 由 Jetbrains 开发&#xff0c;提供 Apache 2 许可社区版和商业版。它是一种智能的上下文感知 IDE&#xff0c;可用于在各种应用程序…

微服务电商平台番外篇一:常用的docker命令

Docker入门手册 Docker 镜像常用命令 搜索镜像 docker search java 下载镜像 docker pull java:8docker pull macro/eureka-server:0.0.1列出镜像 docker images 删除镜像 docker rmi javadocker rmi -f javadocker rmi -f $(docker images)查看镜像 Docker 容器常用命令…

Java基础-内部类与异常处理

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 一、Java 内部类 什么是内部类&#xff1f; 使用内部类的优点 访问局部变量的限制 内部类和继承 内部…