首页 > 编程语言 >C++基础之移动语义(Move Semantic)

C++基础之移动语义(Move Semantic)

时间:2025-01-02 23:26:00浏览次数:3  
标签:std Move Semantic move C++ other 移动 data

文章目录

引言

在现代 C++ 中, Move 语义通过优化资源管理和减少内存复制, 显著提升了程序性能. 它在处理动态内存分配和容器操作等大数据场景中尤为重要. 本文将通过多个实用案例, 深入探讨 Move 语义的核心概念和应用场景.


Move 语义的定义与优点

传统 C++ 中的拷贝语义(Copy Semantics)通常通过构造函数和赋值运算符来复制对象的内容. 然而, 当对象包含动态分配的资源时, 深拷贝会带来高明性能应用. Move 语义通过移动资源的所有权, 避免了这些消耗.

示例: 拷贝和移动操作

std::vector<std::string> coll;
std::string str = "example";

// 拷贝操作
coll.push_back(str); // str内容被拷贝

// 移动操作
coll.push_back(std::move(str)); // str的内容被移动

在上述示例中, std::move将对象转化为右值引用, 使其内容被高效迁移.


应用场景

如果一个对象的值不再被继续使用, 则可以考虑使用 Move. 临时对象是一个特别好的例子.

#include <string>
#include <vector>

std::string GetStr() { return "Hello"; }
int main() {
  std::vector<std::string> vec;
  auto s = GetStr();

  vec.emplace_back(s);             // copy
  vec.emplace_back(s + "World");   // move
  vec.emplace_back(GetStr());      // move
  vec.emplace_back(std::move(s));  // move
  return 0;
}

Move 后的变量

C++标准规定, 被移动对象仍然必须保持在有效但未指定的状态(valid but unspecified state). 这意味着您可以安全地访问被移动对象, 只是不能依赖它的内容.

#include <iostream>
#include <string>
#include <vector>

int main() {
  std::vector<std::string> vec;
  std::string s = "Hello";

  vec.emplace_back(std::move(s));  // move
  std::cout << s << std::endl;     // s依旧可用, 但不确定其值
  s = "world";                     // OK, 可以被重新赋值
  std::cout << s << std::endl;     // OK, s目前有明确的值
  return 0;
}

自定义类中如何支持 Move

一个自定义类通过定义 move 构造函数和 move 赋值运算符来实现对 move 语义的支持.

std::move并不真正移动对象, 而是将对象转化为右值引用(rvalue reference), 为调用相应的移动构造函数或移动赋值运算符提供条件.

#include <algorithm>

class MyString {
 private:
  char* data;
  size_t length;

 public:
  // 移动构造函数
  MyString(MyString&& other) noexcept : data(other.data), length(other.length) {
    other.data = nullptr;  // 防止析构时释放原资源
    other.length = 0;
  }

  // 移动赋值运算符
  MyString& operator=(MyString&& other) noexcept {
    if (this != &other) {
      delete[] data;  // 释放已有资源
      data = other.data;
      length = other.length;
      other.data = nullptr;  // 防止析构时释放原资源
      other.length = 0;
    }
    return *this;
  }

  // 拷贝构造函数
  MyString(const MyString& other)
      : data(new char[other.length]), length(other.length) {
    std::copy(other.data, other.data + other.length, data);
  }

  // 析构函数
  ~MyString() { delete[] data; }
};

Move 语义与noexcept共同使用可确保移动操作的安全性. 例如:
在容器重分配内存时, 如果移动构造函数是noexcept的, 标准库会优先使用 Move 语义, 而非拷贝.


Move 语义的误区

std::move 并不移动数据

它仅将对象标记为右值, 具体移动操作由类的实现进行.

移动后继续使用对象

例如:

std::string s1 = "abc";
auto s2 = std::move(s1);

auto c = s1[0]; // Error

对整型或者布尔类型无效

对于int, float, bool等这些类型的 move 没有意义. 这些基础类型的赋值就是拷贝. 同理, 对于像下面这种内部只含有基础类型的 move 也没有意义, 就是拷贝.

struct POD {
  int a;
  int b;
  bool c;
};

POD a;
POD b = std::move(a); // copy

总结

Move 语义为 C++带来了显著的性能提升, 尤其在需要频繁资源管理的场景中. 理解和正确使用 Move 语义, 是现代 C++ 开发者的一项基本技能. 通过合理设计移动构造函数和移动赋值运算符, 您可以编写出高效且健壮的 C++ 程序.

参考链接

Back to Basics: Move Semantics - Nicolai Josuttis - CppCon 2021
C++ Move Semantics - The Complete Guide

标签:std,Move,Semantic,move,C++,other,移动,data
From: https://blog.csdn.net/arong_xu/article/details/144894737

相关文章

  • C++程序设计谭浩强第四版-第十一章
    第十一章类的继承(面向对象的程序设计)面向对象程序的4大特点抽象封装继承多态派生类(子类)是基类(父类)的具体化,基类是派生类的抽象类的继承:一个新类从已有的类那里获得其已有特性派生:从已有的类(父类)产生一个新的子类class派生类名:[继承方式]基类名{......
  • C++11 thread线程的使用
    C++11thread线程的使用文章目录C++11thread线程的使用构造函数1.`thread()noexcept=default;`2.`thread(thread&)=delete;`3.`thread(constthread&&)=delete;`4.`thread(thread&&__t)noexcept`5.`template<typename_Callable,typename..._Args&g......
  • leetcode热题100(394. 字符串解码)c++
    链接:394.字符串解码给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格......
  • <<零基础学C++,类和对象(上)--类的定义,访问限定符,类域,实例化>>
    目录类的定义访问限定符 类域实例化实例化的概念 类的定义class为定义类的关键字,Date为类的名字(类名就相当于类型),{}中为类的主体,注意类定义结束时后⾯分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的⽅法或者成员......
  • 【算法一周目】位间流转,数字律动——洞察 C++ 位运算中的精妙与哲思
    文章目录常见位运算1.位1的个数2.比特位计数3.汉明距离4.只出现一次的数字5.只出现一次的数字III6.只出现一次的数字II7.判定字符是否唯一8.丢失的数字9.两整数之和10.只出现一次的数字II常见位运算判断一个数的二进制表示的第x位是0还是1(n>>x)&1......
  • DirectX 修复工具 V4.3 绿色增强版:完美解决 DirectX 和 C++ 问题(修复 0xc000007b 错误
    DirectX修复工具V4.3绿色增强版:完美解决DirectX和C++问题简介DirectX修复工具是一款专为检测和修复DirectX问题而设计的实用工具。它能够精准定位问题并进行高效修复,特别是针对0xc000007b错误,拥有极高的修复成功率。本工具支持所有版本DirectX修复,同时增强版还额......
  • C++返回值优化 RVO 和 NRVO
    RVO(ReturnValueOptimization)指的是当函数返回一个临时对象时,编译器会尝试直接将这个临时对象构建在调用者提供的存储空间中,而不是先创建一个临时对象再进行复制。这样就可以避免一次复制操作,提高效率。如:MyClassfunc(){returnMyClass();//返回一个临时对象}......
  • 只谈C++11新特性 - 显式转换函数
    显式转换函数背景与问题在C++11之前,explicit关键字只能用于构造函数。其作用是阻止构造函数在需要隐式转换时被调用。例如:示例问题(C++11之前的explicit用法)#include<iostream>classExample{public:explicitExample(intvalue){std::cout<<......
  • C++中的仿函数
    梅花芳香四溢,我们一往无前文章目录一、仿函数的定义二、仿函数的特性三、仿函数的相对性能优势总结一、仿函数的定义在C++中,仿函数(Functors)或称为函数对象(FunctionObjects)是重载了调用操作符operator()的类或结构体,这使得这些类的对象可以像函数一样被调用。仿......
  • C++多态
    多态概念        用基类指针(同引用)指向从它继承的一组派生类对象,调用派生类的同名覆盖方法,基类指针指向哪个派生类对象,就会调用相应派生类对象的同名覆盖方法,怎么做到的呢?        因为通过基类指针调用派生类的同名覆盖方法时,发生了动态绑定,访问了基类指针......