在线二区人妖系列_国产亚洲欧美日韩在线一区_国产一级婬片视频免费看_精品少妇一区二区三区在线

鍍金池/ 教程/ C/ A.1 右值引用
3.4 本章總結(jié)
6.3 基于鎖設(shè)計更加復雜的數(shù)據(jù)結(jié)構(gòu)
6.1 為并發(fā)設(shè)計的意義何在?
5.2 <code>C++</code>中的原子操作和原子類型
A.7 自動推導變量類型
2.1 線程管理的基礎(chǔ)
8.5 在實踐中設(shè)計并發(fā)代碼
2.4 運行時決定線程數(shù)量
2.2 向線程函數(shù)傳遞參數(shù)
第4章 同步并發(fā)操作
2.3 轉(zhuǎn)移線程所有權(quán)
8.3 為多線程性能設(shè)計數(shù)據(jù)結(jié)構(gòu)
6.4 本章總結(jié)
7.3 對于設(shè)計無鎖數(shù)據(jù)結(jié)構(gòu)的指導建議
關(guān)于這本書
A.1 右值引用
2.6 本章總結(jié)
D.2 &lt;condition_variable&gt;頭文件
A.6 變參模板
6.2 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)
4.5 本章總結(jié)
A.9 本章總結(jié)
前言
第10章 多線程程序的測試和調(diào)試
5.4 本章總結(jié)
第9章 高級線程管理
5.1 內(nèi)存模型基礎(chǔ)
2.5 識別線程
第1章 你好,C++的并發(fā)世界!
1.2 為什么使用并發(fā)?
A.5 Lambda函數(shù)
第2章 線程管理
4.3 限定等待時間
D.3 &lt;atomic&gt;頭文件
10.2 定位并發(fā)錯誤的技術(shù)
附錄B 并發(fā)庫的簡單比較
5.3 同步操作和強制排序
A.8 線程本地變量
第8章 并發(fā)代碼設(shè)計
3.3 保護共享數(shù)據(jù)的替代設(shè)施
附錄D C++線程庫參考
第7章 無鎖并發(fā)數(shù)據(jù)結(jié)構(gòu)設(shè)計
D.7 &lt;thread&gt;頭文件
D.1 &lt;chrono&gt;頭文件
4.1 等待一個事件或其他條件
A.3 默認函數(shù)
附錄A 對<code>C++</code>11語言特性的簡要介紹
第6章 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)設(shè)計
封面圖片介紹
7.2 無鎖數(shù)據(jù)結(jié)構(gòu)的例子
8.6 本章總結(jié)
8.1 線程間劃分工作的技術(shù)
4.2 使用期望等待一次性事件
8.4 設(shè)計并發(fā)代碼的注意事項
D.5 &lt;mutex&gt;頭文件
3.1 共享數(shù)據(jù)帶來的問題
資源
9.3 本章總結(jié)
10.3 本章總結(jié)
10.1 與并發(fā)相關(guān)的錯誤類型
D.4 &lt;future&gt;頭文件
3.2 使用互斥量保護共享數(shù)據(jù)
9.1 線程池
1.1 何謂并發(fā)
9.2 中斷線程
4.4 使用同步操作簡化代碼
A.2 刪除函數(shù)
1.3 C++中的并發(fā)和多線程
1.4 開始入門
第5章 C++內(nèi)存模型和原子類型操作
消息傳遞框架與完整的ATM示例
8.2 影響并發(fā)代碼性能的因素
7.1 定義和意義
D.6 &lt;ratio&gt;頭文件
A.4 常量表達式函數(shù)
7.4 本章總結(jié)
1.5 本章總結(jié)
第3章 線程間共享數(shù)據(jù)

A.1 右值引用

如果你從事過C++編程,你會對引用比較熟悉,C++的引用允許你為已經(jīng)存在的對象創(chuàng)建一個新的名字。對新引用所做的訪問和修改操作,都會影響它的原型。

例如:

int var=42;
int& ref=var;  // 創(chuàng)建一個var的引用
ref=99;
assert(var==99);  // 原型的值被改變了,因為引用被賦值了

目前為止,我們用過的所有引用都是左值引用——對左值的引用。lvalue這個詞來自于C語言,指的是可以放在賦值表達式左邊的事物——在棧上或堆上分配的命名對象,或者其他對象成員——有明確的內(nèi)存地址。rvalue這個詞也來源于C語言,指的是可以出現(xiàn)在賦值表達式右側(cè)的對象——例如,文字常量和臨時變量。因此,左值引用只能被綁定在左值上,而不是右值。

不能這樣寫:

int& i=42;  // 編譯失敗

例如,因為42是一個右值。好吧,這有些假;你可能通常使用下面的方式講一個右值綁定到一個const左值引用上:

int const& i = 42;

這算是鉆了標準的一個空子吧。不過,這種情況我們之前也介紹過,我們通過對左值的const引用創(chuàng)建臨時性對象,作為參數(shù)傳遞給函數(shù)。

其允許隱式轉(zhuǎn)換,所以你可這樣寫:

void print(std::string const& s);
print("hello");  //創(chuàng)建了臨時std::string對象

C++11標準介紹了右值引用(rvalue reference),這種方式只能綁定右值,不能綁定左值,其通過兩個&&來進行聲明:

int&& i=42;
int j=42;
int&& k=j;  // 編譯失敗

因此,可以使用函數(shù)重載的方式來確定:函數(shù)有左值或右值為參數(shù)的時候,看是否能被同名且對應參數(shù)為左值或有值引用的函數(shù)所重載。

其基礎(chǔ)就是C++11新添語義——移動語義(move semantics)。

A.1.1 移動語義

右值通常都是臨時的,所以可以隨意修改;如果知道函數(shù)的某個參數(shù)是一個右值,就可以將其看作為一個臨時存儲或“竊取”內(nèi)容,也不影響程序的正確性。這就意味著,比起拷貝右值參數(shù)的內(nèi)容,不如移動其內(nèi)容。動態(tài)數(shù)組比較大的時候,這樣能節(jié)省很多內(nèi)存分配,提供更多的優(yōu)化空間。試想,一個函數(shù)以std::vector<int>作為一個參數(shù),就需要將其拷貝進來,而不對原始的數(shù)據(jù)做任何操作。C++03/98的辦法是,將這個參數(shù)作為一個左值的const引用傳入,然后做內(nèi)部拷貝:

void process_copy(std::vector<int> const& vec_)
{
  std::vector<int> vec(vec_);
  vec.push_back(42);
}

這就允許函數(shù)能以左值或右值的形式進行傳遞,不過任何情況下都是通過拷貝來完成的。如果使用右值引用版本的函數(shù)來重載這個函數(shù),就能避免在傳入右值的時候,函數(shù)會進行內(nèi)部拷貝的過程,因為可以任意的對原始值進行修改:

void process_copy(std::vector<int> && vec)
{
  vec.push_back(42);
}

如果這個問題存在于類的構(gòu)造函數(shù)中,竊取內(nèi)部右值在新的實例中使用??梢詤⒖家幌虑鍐沃械睦?默認構(gòu)造函數(shù)會分配很大一塊內(nèi)存,在析構(gòu)函數(shù)中釋放)。

清單A.1 使用移動構(gòu)造函數(shù)的類

class X
{
private:
  int* data;

public:
  X():
    data(new int[1000000])
  {}

  ~X()
  {
    delete [] data;
  }

  X(const X& other):  // 1
   data(new int[1000000])
  {
    std::copy(other.data,other.data+1000000,data);
  }

  X(X&& other):  // 2
    data(other.data)
  {
    other.data=nullptr;
  }
};

一般情況下,拷貝構(gòu)造函數(shù)①都是這么定義:分配一塊新內(nèi)存,然后將數(shù)據(jù)拷貝進去。不過,現(xiàn)在有了一個新的構(gòu)造函數(shù),可以接受右值引用來獲取老數(shù)據(jù)②,就是移動構(gòu)造函數(shù)。在這個例子中,只是將指針拷貝到數(shù)據(jù)中,將other以空指針的形式留在了新實例中;使用右值里創(chuàng)建變量,就能避免了空間和時間上的多余消耗。

X類(清單A.1)中的移動構(gòu)造函數(shù),僅作為一次優(yōu)化;在其他例子中,有些類型的構(gòu)造函數(shù)只支持移動構(gòu)造函數(shù),而不支持拷貝構(gòu)造函數(shù)。例如,智能指針std::unique_ptr<>的非空實例中,只允許這個指針指向其對象,所以拷貝函數(shù)在這里就不能用了(如果使用拷貝函數(shù),就會有兩個std::unique_ptr<>指向該對象,不滿足std::unique_ptr<>定義)。不過,移動構(gòu)造函數(shù)允許對指針的所有權(quán),在實例之間進行傳遞,并且允許std::unique_ptr<>像一個帶有返回值的函數(shù)一樣使用——指針的轉(zhuǎn)移是通過移動,而非拷貝。

如果你已經(jīng)知道,某個變量在之后就不會在用到了,這時候可以選擇顯式的移動,你可以使用static_cast<X&&>將對應變量轉(zhuǎn)換為右值,或者通過調(diào)用std::move()函數(shù)來做這件事:

X x1;
X x2=std::move(x1);
X x3=static_cast<X&&>(x2);

想要將參數(shù)值不通過拷貝,轉(zhuǎn)化為本地變量或成員變量時,就可以使用這個辦法;雖然右值引用參數(shù)綁定了右值,不過在函數(shù)內(nèi)部,會當做左值來進行處理:

void do_stuff(X&& x_)
{
  X a(x_);  // 拷貝
  X b(std::move(x_));  // 移動
}
do_stuff(X());  // ok,右值綁定到右值引用上
X x;
do_stuff(x);  // 錯誤,左值不能綁定到右值引用上

移動語義在線程庫中用的比較廣泛,無拷貝操作對數(shù)據(jù)進行轉(zhuǎn)移可以作為一種優(yōu)化方式,避免對將要被銷毀的變量進行額外的拷貝。在2.2節(jié)中看到,在線程中使用std::move()轉(zhuǎn)移std::unique_ptr<>得到一個新實例;在2.3節(jié)中,了解了在std:thread的實例間使用移動語義,用來轉(zhuǎn)移線程的所有權(quán)。

std::thread、std::unique_lock<>、std::future<>std::promise<>std::packaged_task<>都不能拷貝,不過這些類都有移動構(gòu)造函數(shù),能讓相關(guān)資源在實例中進行傳遞,并且支持用一個函數(shù)將值進行返回。std::stringstd::vector<>也可以拷貝,不過它們也有移動構(gòu)造函數(shù)和移動賦值操作符,就是為了避免拷貝拷貝大量數(shù)據(jù)。

C++標準庫不會將一個對象顯式的轉(zhuǎn)移到另一個對象中,除非將其銷毀的時候或?qū)ζ滟x值的時候(拷貝和移動的操作很相似)。不過,實踐中移動能保證類中的所有狀態(tài)保持不變,表現(xiàn)良好。一個std::thread實例可以作為移動源,轉(zhuǎn)移到新(以默認構(gòu)造方式)的std::thread實例中。還有,std::string可以通過移動原始數(shù)據(jù)進行構(gòu)造,并且保留原始數(shù)據(jù)的狀態(tài),不過不能保證的是原始數(shù)據(jù)中該狀態(tài)是否正確(根據(jù)字符串長度或字符數(shù)量決定)。

A.1.2 右值引用和函數(shù)模板

在使用右值引用作為函數(shù)模板的參數(shù)時,與之前的用法有些不同:如果函數(shù)模板參數(shù)以右值引用作為一個模板參數(shù),當對應位置提供左值的時候,模板會自動將其類型認定為左值引用;當提供右值的時候,會當做普通數(shù)據(jù)使用??赡苡行┛谡Z化,來看幾個例子吧。

考慮一下下面的函數(shù)模板:

template<typename T>
void foo(T&& t)
{}

隨后傳入一個右值,T的類型將被推導為:

foo(42);  // foo<int>(42)
foo(3.14159);  // foo<double><3.14159>
foo(std::string());  // foo<std::string>(std::string())

不過,向foo傳入左值的時候,T會被推導為一個左值引用:

int i = 42;
foo(i);  // foo<int&>(i)

因為函數(shù)參數(shù)聲明為T&&,所以就是引用的引用,可以視為是原始的引用類型。那么foo<int&>()就相當于:

foo<int&>(); // void foo<int&>(int& t);

這就允許一個函數(shù)模板可以即接受左值,又可以接受右值參數(shù);這種方式已經(jīng)被std::thread的構(gòu)造函數(shù)所使用(2.1節(jié)和2.2節(jié)),所以能夠?qū)⒖烧{(diào)用對象移動到內(nèi)部存儲,而非當參數(shù)是右值的時候進行拷貝。