05_移动构造和移动赋值运算符

C++为什么要引入移动构造?

我们先来看看临时变量引发的问题。

移动语义的设计本意是为了减少临时变量产生的内存开销。每当我们从函数返回一个临时对象时,它都会被复制。最后创建了一个对象的2个副本,而我们只需要其中的一个。

class Container {
    int* m_Data;

  public:
    Container() {
      //Allocate an array of 20 int on heap
      m_Data = new int[20];
      std::cout << "Constructor: Allocation 20 int" << std::endl;
    }

    ~Container() {
      if (m_Data) {
        delete[] m_Data;
        m_Data = NULL;
      }
      std::cout << "Destructor: Deallocation 20 int" << std::endl;
    }

    //Copy Constructor
    Container(const Container& obj) {
      //Allocate an array of 20 int on heap
      m_Data = new int[20];
      //Copy the data from passed object
      for (int i = 0; i < 20; i++)
        m_Data[i] = obj.m_Data[i];
  
      std::cout << "Copy Constructor: Allocation 20 int" <<  std::endl;
    }
 };

创建对象的一个简易函数

// Create am object of Container and return
Container getContainer() {
  Container obj;
  return obj;
}

创建一个Container向量

int main() {
  // Create a vector of Container Type
  std::vector <Container> vecOfContainers;

  // Add object returned by function into the vector
  vecOfContainers.push_back(getContainer());
  return 0;
}

输出

Constructor: Allocation 20 int        // getContainer()中构造局部变量obj

Copy Constructor: Allocation 20 int   // 返回obj使用的拷贝构造函数

Destructor: free 20 int               // 析构局部变量obj

Copy Constructor: Allocation 20 int   // 临时变量拷贝进入vecOfContainers

Destructor: free 20 int               // 析构临时变量

Destructor: free 20 int               // 程序结束,析构vecOfContainers中的Container变量

从输出可以发现,vector仅使用存放一个Container变量,程序中却要经过2次拷贝构造和析构,这明显造成了很多浪费和无效功。

使用右值引用和移动构造函数解决临时变量问题

Tips: 移动运算符需要添加noexcept 修饰。

移动构造函数采用右值作为输入参数,在移动构造函数内部仅做成员变量所有权的移动,而不是重新分配内存。

Container(Container&& obj) {
    // Just copy the pointer
    m_Data = obj.m_Data;

    // Set the passed object's member to NULL,防止重复释放内存
    obj.m_Data = NULL;
    std::cout << "Move Constructor" << std::endl;
}

现在我们重新运行main中代码,输出如下:

Constructor: Allocation 20 int

Move Constructor

Move Constructor

Destructor: free 20 int

从输出可以看出,原来的两次拷贝构造和析构变为了两次移动构造,这仅涉及到内存的移动,所以节省了工作。

同理,我们还可以实现移动赋值运算符

// Move Assignment Operator

  Container& operator=(Container&& obj) {
    if (this != &obj) {  //**自赋值检测**

    // 这里要**先释放this内的已有内存**
    delete m_Data;

    // Just copy the pointer
    m_Data = obj.m_Data;

    // Set the passed object's member to NULL
    obj.m_Data = NULL;
    std::cout << "Move Assignment Operator" << std::endl;
    }
    return *this;
  }

运行以下代码:

int main() {
 Container obj;
 obj = getContainer();  // Move Assignment will be called
 return 0;
}

输出:(gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0)

临时变量引发的问题

移动语义的设计本意是为了减少临时变量产生的内存开销。每当我们从函数返回一个临时对象时,它都会被复制。最后创建了一个对象的2个副本,而我们只需要其中的一个。

class Container {
    int* m_Data;

  public:
    Container() {
      //Allocate an array of 20 int on heap
      m_Data = new int[20];
      std::cout << "Constructor: Allocation 20 int" << std::endl;
    }

    ~Container() {
      if (m_Data) {
        delete[] m_Data;
        m_Data = NULL;
      }
      std::cout << "Destructor: Deallocation 20 int" << std::endl;
    }

    //Copy Constructor
    Container(const Container& obj) {
      //Allocate an array of 20 int on heap
      m_Data = new int[20];
      //Copy the data from passed object
      for (int i = 0; i < 20; i++)
        m_Data[i] = obj.m_Data[i];
  
      std::cout << "Copy Constructor: Allocation 20 int" <<  std::endl;
    }
 };

创建对象的一个简易函数

// Create am object of Container and return
Container getContainer() {
  Container obj;
  return obj;
}

创建一个Container向量

int main() {
  // Create a vector of Container Type
  std::vector <Container> vecOfContainers;

  // Add object returned by function into the vector
  vecOfContainers.push_back(getContainer());
  return 0;
}

输出

Constructor: Allocation 20 int        // getContainer()中构造局部变量obj

Copy Constructor: Allocation 20 int   // 返回obj使用的拷贝构造函数

Destructor: free 20 int               // 析构局部变量obj

Copy Constructor: Allocation 20 int   // 临时变量拷贝进入vecOfContainers

Destructor: free 20 int               // 析构临时变量

Destructor: free 20 int               // 程序结束,析构vecOfContainers中的Container变量

从输出可以发现,vector仅使用存放一个Container变量,程序中却要经过2次拷贝构造和析构,这明显造成了很多浪费和无效功。

使用右值引用和移动构造函数解决临时变量问题

Tips: 移动运算符需要添加noexcept 修饰。

移动构造函数采用右值作为输入参数,在移动构造函数内部仅做成员变量所有权的移动,而不是重新分配内存。

Container(Container&& obj) {
    // Just copy the pointer
    m_Data = obj.m_Data;

    // Set the passed object's member to NULL,防止重复释放内存
    obj.m_Data = NULL;
    std::cout << "Move Constructor" << std::endl;
}

现在我们重新运行main中代码,输出如下:

Constructor: Allocation 20 int

Move Constructor

Move Constructor

Destructor: free 20 int

从输出可以看出,原来的两次拷贝构造和析构变为了两次移动构造,这仅涉及到内存的移动,所以节省了工作。

同理,我们还可以实现移动赋值运算符

// Move Assignment Operator

  Container& operator=(Container&& obj) {
    if (this != &obj) {  //**自赋值检测**

    // 这里要**先释放this内的已有内存**
    delete m_Data;

    // Just copy the pointer
    m_Data = obj.m_Data;

    // Set the passed object's member to NULL
    obj.m_Data = NULL;
    std::cout << "Move Assignment Operator" << std::endl;
    }
    return *this;
  }

运行以下代码:

int main() {
 Container obj;
 obj = getContainer();  // Move Assignment will be called
 return 0;
}

输出:(gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0)

输出结果与你预想的是否一致呢?

问答

移动构造能否使用const &&?

移动构造函数的核心目的是将源对象的资源所有权转移到目标对象,这通常涉及修改源对象的状态,例如将源对象的指针置为nullptr 以避免资源的重复释放。而 const 修饰的对象是不可修改的,使用 const && 会导致在移动构造函数中无法修改源对象的状态,从而无法完成资源的转移。

参考

https://thispointer.com/c11-move-contsructor-rvalue-references/

输出结果与你预想的是否一致呢?

问答

移动构造能否使用const &&?

移动构造函数的核心目的是将源对象的资源所有权转移到目标对象,这通常涉及修改源对象的状态,例如将源对象的指针置为nullptr 以避免资源的重复释放。而 const 修饰的对象是不可修改的,使用 const && 会导致在移动构造函数中无法修改源对象的状态,从而无法完成资源的转移。

参考

https://thispointer.com/c11-move-contsructor-rvalue-references/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值