IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> move ctor -> 正文阅读

[C++知识库]move ctor

问题描述:

class 在拷贝过程中可能调用 默认构造函数和拷贝构造函数
这样可能(由2次拷贝)导致空间资源浪费

解决方法:用 移动语义 来解决。

在此示例中,只需写一个 move 构造函数,这和 copy构造函数相似,
除了它接收的是一个右值(临时值)之外

代码展示:

main1.cpp 复现 2 次 copy过程
main2.cpp 使用 move 构造函数对照(把 copy构造函数注释掉)

// main1.cpp

#include <iostream>
#include <cstring>

class String{
public:
  String( ) = default;
  // 默认构造函数
  String( const char* string ){
    std::cout << "Created! " << std::endl;
    m_Size = strlen( string );
    m_Data = new char[m_Size];
    memcpy( m_Data, string, m_Size );
  }
  // copy 构造函数
  String( const String& other ){
    std::cout << "Copy! " << std::endl;
    m_Size = other.m_Size;
    m_Data = new char[m_Size];
    memcpy( m_Data, other.m_Data, m_Size );
  }

  // 析构函数
  ~String( ){
    std::cout << "Destroyed!" << std::endl;
    delete[] m_Data;
  }

 void Print_String( ){
    for( uint32_t i = 0; i < m_Size; i ++ ){
      std::cout << m_Data[i];
    }
    std::cout << std::endl;
  }

private:
  char*   m_Data;
  uint32_t m_Size;
};

class Entity{
public:
  Entity( const String& name ) : m_Name( name ) {}
  void Print_Entity( ){
    m_Name.Print_String();
  }

private:
  String m_Name;
};

int main( ){
/**
 \* 解释:
 \*    这里会首先 构造 class String, 
 \*    然后调用copy构造函数 给 class Entity 
 \* 函数输出:
 \*    Created! 
 \*    Copy!
 \*    Destroyed!
 \*    Jeffy_Gao
 \*    Destroyed!
*/
  std::cout << "test main1.cpp" << std::endl;
  Entity entity( "Jeffy_Gao" );
  entity.Print_Entity();
	return 0;
}

问题描述:

class 在拷贝过程中可能调用 默认构造函数和拷贝构造函数
这样可能(由2次拷贝)导致空间资源浪费

解决方法:用 移动语义 来解决。

在此示例中,只需写一个 move 构造函数,这和 copy构造函数相似,
除了它接收的是一个右值(临时值)之外。
可以看到,实际只开辟了一块内存空间(而不是2块)

代码展示:

main1.cpp 复现 2 次 copy过程
main2.cpp 使用 move 构造函数对照(把 copy构造函数注释掉)

// main2.cpp

#include <iostream>
#include <cstring>

class String{
public:
  String( ) = default;

  // 默认构造函数
  String( const char* string ){
    std::cout << "Created! " << std::endl;
    m_Size = strlen( string );
    m_Data = new char[m_Size];
    memcpy( m_Data, string, m_Size );
  }

  // copy 构造函数
  String( const String& other ){
    std::cout << "Copy! " << std::endl;
    m_Size = other.m_Size;
    m_Data = new char[m_Size];
    memcpy( m_Data, other.m_Data, m_Size );
  }

// move 构造函数
// 通过指定 move 构造函数,希望在执行“复制”时,不会复制内存,而是变成移动
// 当没有 move ctor时系统会调用 copy ctor; 有时,则调用 move ctor
// noexcept 运算符进行编译时检查,若表达式声明为不抛出任何异常则返回true。
  String( String&& other ) noexcept{
    std::cout << "Move! " << std::endl;

// 在这里直接复制数据,而不是分配新的内存缓冲区
// 现在这两个指针其实指向 同一块内存区了
    m_Size = other.m_Size;
    m_Data = other.m_Data;  

/**在这里的时候引出一个问题:
\* 因为我们此刻仍然有 2 个字符串实例(m_Data, other.m_Data),当旧的
\* 被删除之后会发生什么? => 它会把数据带走
\* 我们控制了 other.m_Data,偷走了 other.m_Data的数据
\* 因此:还需要处理另一个字符串: 
*/
    other.m_Size = 0;
    other.m_Data = nullptr;
/**
 \* 这样就只是接管了旧字符串(other.m_Data),而不是通过复制它所有的数据和
 \* 分配新的内存来进行深拷贝。(这里相当于做了一个浅拷贝)
*/
  }

  // 析构函数
  ~String( ){
    std::cout << "Destroyed!" << std::endl;
/**
 \*  当旧字符串实例被销毁后, delete m_Data会删除 other.m_Data 的nullptr
 \*  
*/     
    delete m_Data;
  }

  void Print_String( ){
    for( uint32_t i = 0; i < m_Size; i ++ ){
      std::cout << m_Data[i];
    }
    std::cout << std::endl;
  }

private:
  char*   m_Data;
  uint32_t m_Size;
};

class Entity{
public:
  Entity( const String& name ) : m_Name( name ) {}

/** 这里做了相应更改,让其能够接收右值。下面两个写法一样
 \*    Entity( String&& name ) : m_Name( (String&&)name ) {}
 \* 另外,剖析:
 \*    name在传参的时候已经声明了 String &&,右值,为什么函数体内还要 std::move(name)?
 \* =>  右值引用在进入函数体内后会变成左值。用专业的说法叫 "引用折叠"
 \* 示例:
 \*    int &&rr1 = 42; // 正确
 \*    int &&rr2 = rr1; // 错误
 \*    int &&rr3 = std::move(rr1); // 正确
 \* 
 \* 具体解释:
 \*    因为右值引用在进入函数体内的之后, 参数类型会变为左值
 \*    也就是在函数体内你可以对 String&& name中的 name取 &
 \*    所以要触发移动语义必须让他变为右值引用, std::move 就是干这个转换的
 \*    你看std::move的代码 就是干了一个找到源参数类型的static_cast转换
 \*    把参数换成了 &&类 型。所以就能触发后面的移动构造函数
*/

  Entity( String&& name ) : m_Name( std::move(name) ) {}
  void Print_Entity( ){
    m_Name.Print_String();
  }
private:
  String m_Name;
};

int main( ){

/**
 \* 解释:
 \*    这里会首先 构造 class String, 
 \*    然后调用copy构造函数 给 class Entity 
 \* 函数输出:
 \*    Created!    "Jeffy_Gao" 被实例 
 \*    Move!     "Jeffy_Gao"传入 Entity,这时候不需要重新开辟堆上的空间
 \*    Destroyed!   "Jeffy_Gao" 被析构
 \*    Jeffy_Gao   entity.Print_Entity()
 \*    // 程序结束后还有一个 Destroyed, 是 Entity里的 m_Name 的析构
*/

  std::cout << "test main2.cpp" << std::endl;
  Entity entity( "Jeffy_Gao" );
  entity.Print_Entity();

  return 0;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-29 11:55:13  更:2022-04-29 11:55:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 21:16:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码