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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值