【C++11】自己封装RAII类,有哪些坑点?带你了解移动语义的真相

文章目录

  • 一、持有资源的类定义移动构造函数的要点
    • 1.普通内置类型与std::move
    • 2.常见的容器与std::move
    • 3.结构体:
    • 4.智能指针与std::move
  • 参考

一、持有资源的类定义移动构造函数的要点

1.普通内置类型与std::move

在C++中,std::move 主要用于对象的移动语义,对于大多数内置类型(如整数),移动语义实际上没有意义。对于内置类型(如整数、浮点数等),移动与复制的成本是相同的,因为它们的大小是固定且已知的,且复制的成本非常低廉。因此,使用 std::move 对这些类型没有实际的优化效果。

尽管如此,使用 std::move 来处理整数变量是完全合法的,但它不会带来任何性能上的优势。下面是一个简单的例子:

#include <iostream>
#include <utility> // for std::move

void printAndMove(int &&num) {
    std::cout << "Value: " << num << std::endl;
}

int main() {
    int a = 42;
    printAndMove(std::move(a));

    // a 的值仍然是 42,因为对于内置类型没有“移动”语义
    std::cout << "Value of a after move: " << a << std::endl;

    return 0;
}

总结:

  • 对于像整数这样的内置类型,使用 std::move 没有实际的性能收益。
  • 对于更复杂的类型(如 std::string、std::vector 等),std::move 可以避免昂贵的复制操作,通过转移资源提高性能。

2.常见的容器与std::move

#include <iostream>
#include <string>
#include <utility> // for std::move

int main() {
    std::string original = "Hello, world!";
    std::string moved_to = std::move(original);

    std::cout << "Moved-to string: " << moved_to << std::endl;
    std::cout << "Original string after move: " << original << std::endl;

    return 0;
}

Program returned: 0
Program stdout
Moved-to string: Hello, world!
Original string after move: 

在这段代码中:

  • 创建并初始化一个名为 original 的 std::string,内容为 “Hello, world!”。
  • 使用 std::move 函数将 original 转换为右值引用,从而允许内容被移动到 moved_to。
  • 移动之后,moved_to 包含 “Hello, world!”,而 original 处于有效但未指定的状态。通常来说,这意味着 original 变为空字符串。

3.结构体:

对于普通结构体,std::move 可以有效地将资源从一个对象转移到另一个对象。这对于结构体内部包含动态分配的资源(如动态数组或其他需要管理内存的成员变量)时尤为重要。在这种情况下,通过使用 std::move,可以避免昂贵的复制操作,提高性能。

#include <iostream>
#include <vector>
#include <utility> // for std::move

struct MyStruct {
    std::vector<int> data;

    MyStruct(std::vector<int> d) : data(std::move(d)) {}
    
    // 移动构造函数
    MyStruct(MyStruct&& other) noexcept : data(std::move(other.data)) {
        std::cout << "Move constructor called" << std::endl;
    }
    
    // 移动赋值运算符
    MyStruct& operator=(MyStruct&& other) noexcept {
        if (this != &other) {
            data = std::move(other.data);
            std::cout << "Move assignment operator called" << std::endl;
        }
        return *this;
    }
    
    // 禁用复制构造函数和复制赋值运算符
    MyStruct(const MyStruct&) = delete;
    MyStruct& operator=(const MyStruct&) = delete;
};

int main() {
    MyStruct s1(std::vector<int>{1, 2, 3, 4, 5});
    
    // 使用 std::move 将 s1 转移到 s2
    MyStruct s2 = std::move(s1);
    
    std::cout << "s1 data size after move: " << s1.data.size() << std::endl;
    std::cout << "s2 data size after move: " << s2.data.size() << std::endl;
    
    MyStruct s3(std::vector<int>{6, 7, 8, 9, 10});
    
    // 使用 std::move 将 s3 转移到 s2
    s2 = std::move(s3);
    
    std::cout << "s3 data size after move: " << s3.data.size() << std::endl;
    std::cout << "s2 data size after move assignment: " << s2.data.size() << std::endl;

    return 0;
}

Program returned: 0
Program stdout
Move constructor called
s1 data size after move: 0
s2 data size after move: 5
Move assignment operator called
s3 data size after move: 0
s2 data size after move assignment: 5

对于仅包含普通内置类型的结构体,std::move 虽然是合法的,但实际上没有什么效果,因为内置类型的移动与复制是一样的。内置类型(如 int、double 等)的复制开销很低,并且移动这些类型不会带来性能提升。

但是,如果你定义了移动构造函数和移动赋值运算符,可以确保你的结构体在更复杂的情况下(例如成员类型改变)也能正确处理移动语义。

#include <iostream>
#include <utility> // for std::move

struct MyStruct {
    int a;
    double b;

    // 默认构造函数
    MyStruct(int x, double y) : a(x), b(y) {}

    // 移动构造函数
    MyStruct(MyStruct&& other) noexcept : a(other.a), b(other.b) {
        std::cout << "Move constructor called" << std::endl;
        // 这里可以将其他对象的值重置,但通常没有必要
    }

    // 移动赋值运算符
    MyStruct& operator=(MyStruct&& other) noexcept {
        if (this != &other) {
            a = other.a;
            b = other.b;
            std::cout << "Move assignment operator called" << std::endl;
            // 这里可以将其他对象的值重置,但通常没有必要
        }
        return *this;
    }

    // 禁用复制构造函数和复制赋值运算符
    MyStruct(const MyStruct&) = delete;
    MyStruct& operator=(const MyStruct&) = delete;
};

int main() {
    MyStruct s1(42, 3.14);
    
    // 使用 std::move 将 s1 转移到 s2
    MyStruct s2 = std::move(s1);

    std::cout << "s1.a after move: " << s1.a << std::endl; // 值仍然存在,但不再关心
    std::cout << "s1.b after move: " << s1.b << std::endl; // 值仍然存在,但不再关心
    std::cout << "s2.a after move: " << s2.a << std::endl;
    std::cout << "s2.b after move: " << s2.b << std::endl;

    MyStruct s3(100, 2.71);

    // 使用 std::move 将 s3 转移到 s2
    s2 = std::move(s3);

    std::cout << "s3.a after move: " << s3.a << std::endl; // 值仍然存在,但不再关心
    std::cout << "s3.b after move: " << s3.b << std::endl; // 值仍然存在,但不再关心
    std::cout << "s2.a after move assignment: " << s2.a << std::endl;
    std::cout << "s2.b after move assignment: " << s2.b << std::endl;

    return 0;
}

Move constructor called
s1.a after move: 42
s1.b after move: 3.14
s2.a after move: 42
s2.b after move: 3.14
Move assignment operator called
s3.a after move: 100
s3.b after move: 2.71
s2.a after move assignment: 100
s2.b after move assignment: 2.71

4.智能指针与std::move

在C++中,std::move 对于智能指针(如 std::unique_ptr 和 std::shared_ptr)非常有用,因为智能指针管理动态分配的资源,如内存。通过使用 std::move,可以将智能指针的所有权从一个对象转移到另一个对象,而无需复制底层资源。这有助于避免资源泄漏并确保资源的唯一所有权。

#include <iostream>
#include <memory> // for std::unique_ptr and std::make_unique

void processUniquePtr(std::unique_ptr<int> ptr) {
    std::cout << "Processing unique pointer with value: " << *ptr << std::endl;
}

int main() {
    std::unique_ptr<int> myPtr = std::make_unique<int>(42);

    // 使用 std::move 转移 myPtr 的所有权到 processUniquePtr
    processUniquePtr(std::move(myPtr));

    // myPtr 此时不再拥有资源,应该为 nullptr
    if (myPtr == nullptr) {
        std::cout << "myPtr is now nullptr after move" << std::endl;
    }

    return 0;
}

在这个例子中:

  • 创建了一个 std::unique_ptr 类型的智能指针 myPtr,并使用 std::make_unique 进行初始化,指向一个值为 42 的整数。
  • 使用 std::move(myPtr) 将 myPtr 的所有权转移给 processUniquePtr 函数。
  • 在 processUniquePtr 函数中,打印出智能指针指向的值。
  • 在所有权转移后,myPtr 被设置为 nullptr,因为它不再拥有资源。
#include <iostream>
#include <memory> // for std::shared_ptr and std::make_shared

void processSharedPtr(std::shared_ptr<int> ptr) {
    std::cout << "Processing shared pointer with value: " << *ptr << std::endl;
    std::cout << "Shared pointer use count: " << ptr.use_count() << std::endl;
}

int main() {
    std::shared_ptr<int> myPtr = std::make_shared<int>(42);

    std::cout << "Initial use count: " << myPtr.use_count() << std::endl;

    // 使用 std::move 转移 myPtr 的所有权到 processSharedPtr
    processSharedPtr(std::move(myPtr));

    // myPtr 此时仍然是有效的,但 use_count 应该减少
    if (myPtr == nullptr) {
        std::cout << "myPtr is now nullptr after move" << std::endl;
    } else {
        std::cout << "myPtr is still valid after move, use count: " << myPtr.use_count() << std::endl;
    }

    return 0;
}

在这个例子中:

  • 创建了一个 std::shared_ptr 类型的智能指针 myPtr,并使用 std::make_shared 进行初始化,指向一个值为 42 的整数。
  • 打印初始的引用计数。
  • 使用 std::move(myPtr) 将 myPtr 的所有权转移给 processSharedPtr 函数。
  • 在 processSharedPtr 函数中,打印出智能指针指向的值和当前的引用计数。
  • 在所有权转移后,myPtr 仍然有效,但引用计数应减少。

总结:

  • std::move 对于智能指针(尤其是 std::unique_ptr)非常有用,可以转移所有权而不是复制资源。
  • 对于 std::shared_ptr,使用 std::move 可以减少引用计数操作的开销,但共享资源的所有权仍然被多个智能指针共享。

参考

  • 【C++11】自己封装RAII类,有哪些坑点?带你了解移动语义的真相
  • move

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Andrej Karpathy提出未来计算机2.0构想: 完全由神经网络驱动!网友炸锅了

昨天凌晨&#xff0c;知名人工智能专家、OpenAI的联合创始人Andrej Karpathy提出了一个革命性的未来计算机的构想&#xff1a;完全由神经网络驱动的计算机&#xff0c;不再依赖传统的软件代码。 嗯&#xff0c;这是什么意思&#xff1f;全部原生LLM硬件设备的意思吗&#xff1f…

机械设备制造企业MES系统解决方案介绍

机械设备制造行业涵盖了各类工业设备、工程机械、农业机械等多个领域&#xff0c;对生产精度、质量控制和效率提出了较高要求。为了提升生产效率、保证产品质量并满足客户需求&#xff0c;越来越多的机械设备制造企业引入了MES系统。本文将详细介绍MES系统在机械设备制造行业的…

魔镜魔镜,我要变得更漂亮!按需搭配一键叠穿,效果拿下新SOTA!中山大学字节智创数字人团队提出虚拟试穿新框架

魔镜魔镜,我要变得更漂亮!按需搭配一键叠穿,效果拿下新SOTA!中山大学&字节智创数字人团队提出虚拟试穿新框架。 多件衣服按指定穿法一键虚拟试穿! 中山大学&字节智创数字人团队提出了一个名为MMTryon的虚拟试穿框架,可以通过输入多个服装图像及指定穿法的文本指…

COB封装的LED显示屏是什么?

COB&#xff08;Chip on Board&#xff09;封装的LED显示屏&#xff0c;是一种采用先进倒装COB封装技术的显示屏&#xff0c;其中LED芯片是直接被安装并封装在PCB电路板上&#xff0c;而不是先对单个封装再焊接至电路板&#xff0c;与SMD&#xff08;Surface Mount Device&…

怎么快速给他人分享图片?扫描二维码看图的简单做法

现在通过二维码来查看图片是一种很常见的方法&#xff0c;通过二维码来查看图片不仅能够减少对手机存储空间的占用&#xff0c;而且获取图片变得更加方便快捷&#xff0c;只需要扫码就能够查看图片&#xff0c;有利于图片的展现。很多的场景中都有图片二维码的应用&#xff0c;…

2024软件设计师经验贴(一考就过)

2024软件设计师经验贴&#xff08;一考就过&#xff09; 第一阶段、基础积累&#xff1a;把书读厚 这一阶段可以跟着视频、书籍或文章进行基础知识的学习。 推荐的视频系列&#xff1a; 「软件设计师」 上午题 #1 计算机系统_哔哩哔哩_bilibili 40–14.3设计模式 推荐的文…

下载和使用SLUN数据集

1. 下载数据集 网址在https://opendatalab.com/OpenDataLab/lsun/tree/main/raw/scenes 下载bedroom_val_lmdb.zip 然后解压后会又两个文件&#xff0c;一个data.mdb&#xff0c;另一个lock.mdb。 2. 使用torchvison使用LSUN数据集 我把解压后的bedroom_val_lmdb放在/home/…

3.js - 深度测试、深度写入、深度函数

md&#xff0c;艹&#xff0c;这玩意得理解&#xff0c;只看代码不管事 效果图 代码 // ts-nocheck// 引入three.js import * as THREE from three// 导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls// 导入lil.gui import { GUI } …

山东访老友 | 同成建材数字化转型两年前后的巨大变化

山东省济宁市同成建材有限公司&#xff08;简称“同成建材”&#xff09;成立于2013年&#xff0c;目前共建有混凝土生产线2条、砂浆生产线1条&#xff0c;是济宁市最早建成的预拌混凝土搅拌站之一。 2020年&#xff0c;同成建材产量增长&#xff0c;市场环境变化&#xff0c;…

Redis的使用(二)redis的命令总结

1.概述 这一小节&#xff0c;我们主要来研究一下redis的五大类型的基本使用&#xff0c;数据类型如下&#xff1a; redis我们接下来看一看这八种类型的基本使用。我们可以在redis的官网查询这些命令:Commands | Docs,同时我们也可以用help 数据类型查看命令的帮助文档。 2. 常…

【漏洞复现】D-Link NAS 未授权RCE漏洞(CVE-2024-3273)

0x01 产品简介 D-Link 网络存储 (NAS)是中国友讯&#xff08;D-link&#xff09;公司的一款统一服务路由器。 0x02 漏洞概述 D-Link NAS nas_sharing.cgi接口存在命令执行漏洞&#xff0c;该漏洞存在于“/cgi-bin/nas_sharing.cgi”脚本中&#xff0c;影响其 HTTP GET 请求处…

STM32F1+HAL库+FreeTOTS学习3——任务创建(动态和静态两种)

STM32F1HAL库FreeTOTS学习3——任务创建&#xff08;动态和静态两种&#xff09; 任务创建API函数任务创建流程代码实现1. 动态任务创建和删除2. 静态任务创建和删除 上期我们学习了STM32移植FreeRTOS搭建基准工程&#xff0c;现在我们来学习任务创建 任务创建API函数 前面我们…

大数据可视化实验(八):大数据可视化综合实训

目录 一、实验目的... 1 二、实验环境... 1 三、实验内容... 1 1&#xff09;Python纵向柱状图实训... 1 2&#xff09;Python水平柱状图实训... 3 3&#xff09;Python多数据并列柱状图实训.. 3 4&#xff09;Python折线图实训... 4 5&#xff09;Python直方图实训...…

Redis---保证主从节点一致性问题 +与数据库数据保持一致性问题

保证主从节点一致性问题 Redis的同步方式默认是异步的&#xff0c;这种异步的同步方式导致了主从之间的数据存在一定的延迟&#xff0c;因此Redis默认是弱一致性的。 解决&#xff1a; 1.使用Redisson这样的工具&#xff0c;它提供了分布式锁的实现&#xff0c;确保在分布式环…

搭贝这个低代码开发平台靠谱吗?

在应用开发领域&#xff0c;低代码开发平台因其拖拽式的操作给用户带来了极大的便利和灵活性。根据相关调查数据&#xff0c;2022年国内低代码开发平台已超过100家。搭贝在众多低代码平台中也享有一定的知名度。那么&#xff0c;搭贝究竟怎么样&#xff0c;是否值得信赖&#x…

Dify入门指南

一.Dify介绍 生成式 AI 应用创新引擎&#xff0c;开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力&#xff0c;轻松构建和运营生成式 AI 原生应用&#xff0c;比 LangChain 更易用。一个平台&#xff0c;接入全球大型语言模型。不同…

IDEA Debug 断点

今天在工作发现有些新入职的小伙伴们&#xff0c;在调试程序时不是很会正确使用IDEA所提供Breakpoints(断点)&#xff0c;这里就简单的介绍下比较常用的功能。 快捷键&#xff1a; 切换行断点&#xff1a;Ctrl F8 编辑断点属性&#xff1a;Ctrl Shift F8 断点的类型 行断点&am…

Google地图获取位置的前端代码与测试

test.html <script src"http://maps.google.com/maps/api/js?sensorfalse"></script> <script > if (navigator.geolocation) {  console.log(Geolocation is supported!);// var startPos;var geoSuccess function(position) {startPos p…

Codeforces Round 954 (Div. 3)(A~E)

目录 A. X Axis B. Matrix Stabilization C. Update Queries D. Mathematical Problem A. X Axis Problem - A - Codeforces 直接找到第二大的数&#xff0c;答案就是这个数与其他两个数的差值的和。 void solve() {vector<ll>a;for (int i 1; i < 3; i){int x;…

【C++知识点总结全系列 (02)】:C++中的语句、运算符和表达式详细总结

文章目录 1、语句(1)简单语句A.空语句B.复合语句 (2)条件语句(3)迭代语句A.常规for循环B.范围for循环C.while和do...while (4)跳转语句A.break语句B.continue语句C.goto语句 (5)异常处理语句A.标准异常B.throw抛出异常 (6)try语句 2、运算符(1)算术运算符(2)关系运算符(3)逻辑运…