標籤:img 調用 swa copy using struct 使用 span 分享
概述
從 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的值被釋放掉了。
總結
調試過後,我們發現,賦值運算的過程並非像想象中那麼簡單,是不是?複製賦值還是開闢一個臨時變數用於轉化,這個耗費了額外的空間資源。
移動賦值就可以避免這個問題,但是需要注意的是,移動賦值使用的是右值,用完之後就被銷毀了,所以,如果想把一個左值當做右值來用,必須確保這個左值在這之後不需要使用了。
參考:
- 《C++ primer》
“複製賦值”和“移動賦值”的思考