“復制賦值”和“移動賦值”的思考


概述

從 C++ 11 中開始,該語言支持兩種類型的分配:復制賦值移動賦值。其中的內部細節是咋樣的呢?今天跟蹤了一下,是個蠻有趣的過程。下面我們以一個簡單的類來做個分析。

#ifndef HASPTR_H
#define HASPTR_H


#include <string>

class HasPtr {
public:
    friend void swap(HasPtr&, HasPtr&);
    HasPtr(const std::string& s = std::string());
    HasPtr(const HasPtr& hp);
    HasPtr(HasPtr&& p) noexcept;
    HasPtr& operator=(HasPtr rhs);
    // HasPtr& operator=(const HasPtr &rhs);
    // HasPtr& operator=(HasPtr &&rhs) noexcept;
    ~HasPtr();

private:
    std::string* ps;
    int i;
};

#endif // HASPTR_H


#include "hasptr.h"
#include <iostream>

inline void swap(HasPtr& lhs, HasPtr& rhs)
{
    using std::swap;
    swap(lhs.ps, rhs.ps);
    swap(lhs.i, rhs.i);
    std::cout << "call swap" << std::endl;
}

HasPtr::HasPtr(const std::string& s) : ps(new std::string(s)), i()
{
    std::cout << "call constructor" << std::endl;
}

//這里的i+1只是為了方便調試的時候看過程,實際是不用加1的
HasPtr::HasPtr(const HasPtr& hp) : ps(new std::string(*hp.ps)), i(hp.i + 1)
{
    std::cout << "call copy constructor" << std::endl;
}

HasPtr::HasPtr(HasPtr&& p) noexcept : ps(p.ps), i(p.i)
{
    p.ps = 0;
    std::cout << "call move constructor" << std::endl;
}

HasPtr& HasPtr::operator=(HasPtr rhs)
{
    swap(*this, rhs);
    return *this;
}

HasPtr::~HasPtr()
{
    std::cout << "call destructor" << std::endl;
    delete ps;
}

主函數

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    HasPtr hp1("hello"), hp2("World"), *pH = new HasPtr("World");
    hp1 = hp2;
    hp1 = std::move(*pH);

    return a.exec();
}

下面我們開始調試:

輸出:

我們通過構造函數構造了三個變量,他們的值和

  address ps i
hp1 0x28fe64 "hello" 0
hp2 0x28fe5c "World" 0
pH 0x28fe9c "World" 0

 

 

 

復制賦值

我們接着單步走:

可以發現首先調用了復制構造函數,構造了一個和hp2一樣的臨時變量

  address ps i
this 0x28fe2c "World" 1
hp2 0x28fe5c "World" 0

 

 

 

下一步:

到這里才開始進行賦值運算,我們對比一下數據:

  address ps i
this 0x28fe2c "hello" 0
rhs 0x28fe8c "World" 1

 

 

 

這里的rhs就是我們剛剛分配的臨時變量,那么this就是hp1,所以最終是我們的臨時變量和hp1交換,我們接着走:

這里lhs的地址就是:0x28fe64,就是hp1的地址,交換之后:

到此hp1和臨時變量的值就完全交換過來了,也就是說hp1 = hp1了。

  address ps i
lhs 0x28fe64 "World" 1
rhs 0x28fe8c "hello" 0

 

 

 

 

可是我們接着運行,發現進入了一個析構函數:

 

看一下地址是0x28fe8c以及其值,這是臨時變量,臨時變量不用了,所以被銷毀了,至此我們的復制賦值運算就結束了。

移動賦值

我們看一下移動賦值賦值:

 

首先進入移動函數,這里只是使指針指向了pH的數據,並未構造新的數據,變量右值引用了pH,只是相當於換了個名字。

接下來開始進入賦值運算:

 

這里兩個交換的值是hp1和pH,和復制賦值不同,它是和臨時變量交換數據,

 

后面進入析構函數:

它釋放掉了pH的數據。

 

可以看出來,pH的值被釋放掉了。

總結

調試過后,我們發現,賦值運算的過程並非像想象中那么簡單,是不是?復制賦值還是開辟一個臨時變量用於轉化,這個耗費了額外的空間資源。

移動賦值就可以避免這個問題,但是需要注意的是,移動賦值使用的是右值,用完之后就被銷毀了,所以,如果想把一個左值當做右值來用,必須確保這個左值在這之后不需要使用了。

 參考:

  1.   《C++ primer》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM