*以下內容為本人的學習筆記,如需要轉載,請宣告原文鏈接 微信公眾號「ENG八戒」https://mp.weixin.qq.com/s/Xd_FwT8E8Yx9Vnb64h6C8w
帶給現代 C++ 性能飛躍的特性很多,今天一邊聊技術,一邊送福利!

過去寫 C/C++ 代碼,大家對資料做傳遞時,都習慣先拷貝再賦值,比如,把資料從 t1 復制到 t2,復制完成后 t2 和 t1 的狀態是一致的,t1 狀態沒變,這里的狀態指的是物件內部的非靜態成員資料集合,
在程式運行程序中,復制程序既要分配空間又要拷貝內容,對于空間和時間都是種損耗,復制操作,無疑是一門很大的開銷,何況經常觸發資源復制的時候,
來看看普通的函式回傳值到底有哪些開銷,
std::string getString()
{
std::string s;
// ...
return s;
}
int main()
{
std::string str = getString();
// ...
}
假設你的編譯器還不支持 C++ 11,那么,在 main() 函式里呼叫 getString() 時,需要在呼叫堆疊里分配臨時物件用于復制 getString() 的回傳值 s,復制完成呼叫 s 的解構式釋放物件,然后,再呼叫 std::string 類的復制賦值運算子函式將臨時物件復制到 str,同時呼叫臨時物件的解構式執行釋放,
那么,有沒有技巧可以實作上面示例代碼同樣的效果,同時避免復制?
有的,就是接下來重點介紹的移動(和中國移動無關),
相對于復制,移動無須重新分配空間和拷貝內容,只需把源物件的資料重新分配給目標物件即可,移動后目標物件狀態與移動前的源物件狀態一致,但是移動后源物件狀態被清空,
實際上,大部份的情況下,資料僅僅需要移動即可,拷貝復制顯得多余,就像,你從圖書館借書,把自己手機的 SIM 卡拔出來再插到其它手機上,去商店買東西你的錢從口袋移動到收銀柜等等,
那么,是不是可以對所有的資料都執行移動?
答案是否定的,在現代 C++ 中,只有右值可以被移動,
左右值概念
在 C++ 11 之前,左右值的劃分比較簡單,只有左值和右值兩種,
但是從 C++ 11 開始,重新把值類別劃分成了五種,左值(lvalue, left value),將亡值(xvalue, expiring value),純右值(prvalue, pure right value),泛左值(glvalue, generalized left value),右值(rvalue, right value),不過后邊的兩種 glvalue 和 rvalue 是基于前面的三種組合而成,從集合概念來看,glvalue 包含 lvalue 和 xvalue,rvalue 包含 xvalue 和 prvalue,
左右值劃分的依據是:具名和可被移動,
具名,簡單點理解就是尋址,可被移動,允許對量的內部資源移動到其它位置,并且保持量自身是有效的,但是狀態不確定,
- lvalue:具名且不可移動
- xvalue:具名且可移動
- prvalue:不具名且可移動
那么,可以看到泛左值(glvalue)其實就是具名的量,右值就是可移動的量,
以往在往函式傳參的時候,經常有用到值參考的模式,形式如下:
function(T& obj)
T 是型別,obj 是引數,
到了現代 C++,原來的值參考就變成了左值參考,另外還出現了右值參考,形式如下:
function(T&& obj)
那么 C++ 11 是怎樣實作移動操作的呢?
實作移動操作
移動操作依賴于類內部特殊成員函式的執行,但前提是該物件是可移動的,如果恰好物件是左值(lvalue)呢?
C++ 11 的標準庫就提供了 std::move() 實作左右值轉換操作,std::move() 用于將運算式從 lvalue(左值) 轉換成 xvalue(將亡值),但不會對數值執行移動,當然,使用強制型別轉換也是可以達到同樣目的,
std::move(obj); // 等價于 static_cast<T&&>(obj);
在 stack overflow 上看到對 std::move() 的一段描述,與其說它是一個函式,不如說,它是編譯器對運算式值評估的方式轉換器,
以往慣常使用 C++ 類定義時,我們都知道有這么幾個特殊的成員函式:
- 默認建構式(default constructor)
- 復制建構式(copy constructor)
- 復制賦值運算子函式(copy assignment operator)
- 解構式(destructor)
來看看一個簡單的例子:
class MB // MemoryBlock
{
public:
// 為下面代碼演示簡單起見
// 在 public 定義成員屬性
size_t size;
char *buf;
// 默認建構式
explicit MB(int sz = 1024)
: size(sz), buf(new char[sz]) {}
// 解構式
~MB() {
if (buf != nullptr) {
delete[] buf;
}
}
// 復制建構式
MB(const MB& obj)
: size(obj.size),
buf(new char[obj.size]) {
memcpy(buf, obj.buf, size);
}
// 復制賦值運算子函式
MB& operator=(const MB& obj) {
if (this != &obj) {
if (buf != nullptr) {
delete[] buf;
}
size = obj.size;
buf = new char[size];
memcpy(buf, obj.buf, size);
}
return *this;
}
}
為了支持移動操作,從 C++ 11 開始,類定義里新增了兩個特殊成員函式:
- 移動建構式(move constructor)
- 移動賦值運算子函式(move assignment operator)
移動建構式
在構造新物件時,如果傳入的引數是右值參考物件,就會呼叫移動建構式創建物件,如果沒有自定義移動建構式,那么編譯器就會自動生成,默認實作是遍歷呼叫成員屬性的移動建構式,并移動右值物件的成員屬性資料到新物件,
定義一般宣告形式如下:
T::T(C&& other);
基于上面的簡單例子:
class MB // MemoryBlock
{
public:
// ...
// 移動建構式
MB(MB&& obj)
: size(0), buf(nullptr) {
// 移動源物件資料到新物件
size = obj.size;
buf = obj.buf;
// 清空源物件狀態
// 避免解構式多次釋放資源
obj.size = 0;
obj.buf = nullptr;
}
}
可見,移動建構式的執行程序,僅僅是簡單賦值的程序,不涉及拷貝資源的耗時操作,自然執行效率大大提高,
移動賦值運算子函式
在呼叫賦值運算子時,如果右邊傳入的引數是右值參考物件,就會呼叫移動賦值運算子函式,同樣,如果沒有自定義移動賦值運算子函式,那么編譯器也會自動生成,默認實作是遍歷呼叫成員屬性的移動賦值運算子函式并移動成員屬性的資料到左邊引數物件,
一般宣告形式如下:
T& T::operator=(C&& other);
基于上面的簡單例子:
class MB // MemoryBlock
{
public:
// ...
// 移動賦值運算子函式
MB& MB::operator=(MB&& obj) {
if (this != &obj) {
if (buf != nullptr) {
delete[] buf;
}
// 移動源物件資料到新物件
size = obj.size;
buf = obj.buf;
// 清空源物件狀態
// 避免解構式多次釋放資源
obj.size = 0;
obj.buf = nullptr;
}
return *this;
}
}
移動賦值運算子函式的執行程序,同樣僅僅是簡單賦值的程序,執行效率明顯遠超復制操作,
總結
回顧文首的示例代碼,由于 C++ 11 加入了回傳值優化 RVO(Return Value Optimization) 的特性,所以代碼無需變更即可獲得效率提升,對于部分編譯器而言,比如 IBM Compiler、Visual C++ 2010 等,已經提前具備回傳值優化的支持,
對于 RVO 的內容,暫不展開討論,有興趣的同學可以關注公眾號【ENG八戒】了解后續更新,關注后甚至可以參與贈書活動!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/554680.html
標籤:其他
上一篇:CentOS7環境編譯python3.9版本pjsua
下一篇:返回列表
