1. std::forward原型
template <typename T> T&& forward(typename std::remove_reference<T>::type& param) //左值引用版本 { return static_cast<T&&>(param); } template <typename T> T&& forward(typename std::remove_reference<T>::type&& param) //右值引用版本 { //param被右值初始化時,T應為右值引用類型,如果T被綁定為左值引用則報錯。 static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type"); return static_cast<T&&>(param); } //其中remove_reference的實現如下 //1. 特化版本(一般的類) template <typename T> struct remove_reference { typedef T type; }; //2. 左值引用版本 template <typename T> struct remove_reference<T&> { typedef T type; }; //3. 右值引用版本 template <typename T> struct remove_reference<T&&> { typedef T type; };
2. 完美轉發(Perfect Forwarding)
(1)完美轉發:是指在函數模板中,完全依照模板的參數類型(即保持實參的左值、右值特性),將實參傳遞給函數模板中調用的另外一個函數。
(2)原理分析
class Widget{}; //完美轉發 template<typename T> void func(T&& fparam) //fparam是個Universal引用 { doSomething(std::forward<T>(fparam)); } //1. 假設傳入func是一個左值的Widget對象, T被推導為Widget&,則forward如下: Widget& && forward(typename std::remove_reference<Widget&>::type& param) { return static_cast<Widget& &&>(param); } //==>引用折疊折后 Widget& forward(Widget& param) { return static_cast<Widget&>(param); } //2. 假設傳入func是一個右值的Widget對象, T被推導為Wiget,則forward如下: Widget&& forward(typename std::remove_reference<Widget>::type& param) { return static_cast<Widget&&>(param); }
(3)std::forward和std::move的聯系和區別
①std::move是無條件轉換,不管它的參數是左值還是右值,都會被強制轉換成右值。就其本身而言,它沒有move任何東西。
②std::forward是有條件轉換。只有在它的參數綁定到一個右值時,它才轉換它的參數到一個右值。當參數綁定到左值時,轉換后仍為左值。
③對右值引用使用std::move,對universal引用則使用std::forward
④如果局部變量有資格進行RVO優化,不要把std::move或std::forward用在這些局部變量中
⑤std::move和std::forward在運行期都沒有做任何事情。
【編程實驗】不完美轉發和完美轉發
#include <iostream> //#include <utility> //for std::forward using namespace std; void print(const int& t) { cout <<"lvalue" << endl; } void print(int&& t) { cout <<"rvalue" << endl; } template<typename T> void Test(T&& v) //v是Universal引用 { //不完美轉發 print(v); //v具有變量,本身是左值,調用print(int& t) //完美轉發 print(std::forward<T>(v)); //按v被初始化時的類型轉發(左值或右值) //強制將v轉為右值 print(std::move(v)); //將v強制轉為右值,調用print(int&& t) } int main() { cout <<"========Test(1)========" << endl; Test(1); //傳入右值 int x = 1; cout <<"========Test(x)========" << endl; Test(x); //傳入左值 cout <<"=====Test(std::forward<int>(1)===" << endl; Test(std::forward<int>(1)); //T為int,以右值方式轉發1 //Test(std::forward<int&>(1)); //T為int&,需轉入左值 cout <<"=====Test(std::forward<int>(x))===" << endl; Test(std::forward<int>(x)); //T為int,以右值方式轉發x cout <<"=====Test(std::forward<int&>(x))===" << endl; Test(std::forward<int&>(x)); //T為int,以左值方式轉發x return 0; } /*輸出結果 e:\Study\C++11\16>g++ -std=c++11 test2.cpp e:\Study\C++11\16>a.exe ========Test(1)======== lvalue rvalue rvalue ========Test(x)======== lvalue lvalue rvalue =====Test(std::forward<int>(1)=== lvalue rvalue rvalue =====Test(std::forward<int>(x))=== lvalue rvalue rvalue =====Test(std::forward<int&>(x))=== lvalue lvalue rvalue */
3.萬能的函數包裝器
(1)利用std::forward和可變參數模板實現
①可將帶返回值、不帶返回值、帶參和不帶參的函數委托萬能的函數包裝器執行。
②Args&&為Universal引用,因為這里的參數可能被左值或右值初始化。Funciont&&也為Universal引用,如被lambda表達式初始化。
③利用std::forward將參數正確地(保持參數的左、右值屬性)轉發給原函數
【編程實驗】萬能的函數包裝器
#include <iostream> using namespace std; //萬能的函數包裝器 //可將帶返回值、不帶返回值、帶參和不帶參的函數委托萬能的函數包裝器執行 //注意:Args&&表示Universal引用,因為這里的參數可能被左值或右值初始化 // Funciont&&也為Universal引用,如被lambda表達式初始化 template<typename Function, class...Args> auto FuncWrapper(Function&& func, Args&& ...args)->decltype(func(std::forward<Args>(args)...)) { return func(std::forward<Args>(args)...); } void test0() { cout << "void test0()" << endl; } int test1() { return 1; } int test2(int x) { return x; } string test3(string s1, string s2) { return s1 + s2; } int main() { FuncWrapper(test0); cout << "int test1(): "; cout << FuncWrapper(test1) << endl; cout << "int test2(int x): " ; cout << FuncWrapper(test2, 1) << endl; cout << "string test3(string s1, string s2): "; cout << FuncWrapper(test3, "aa", "bb") << endl; cout << "[](int x, int y){return x + y;}: "; cout << FuncWrapper([](int x, int y){return x + y;}, 1, 2) << endl; return 0; } /*輸出結果: e:\Study\C++11\16>g++ -std=c++11 test3.cpp e:\Study\C++11\16>a.exe void test0() int test1(): 1 int test2(int x): 1 string test3(string s1, string s2): aabb [](int x, int y){return x + y}: 3 */
(2)emplace_back減少內存拷貝和移動
①emplace_back的實現原理類似於“萬能函數包裝器”,將參數std::forward轉發給元素類的構造函數。實現上,首先為該元素開辟內存空間,然后在這片空間中調用placement new進行初始化,這相當於“就地”(在元素所在內存空間)調用元素對象的構造函數。
②而push_back會先將參數轉為相應的元素類型,這需要調用一次構造函數,再將這個臨時對象拷貝構造給容器內的元素對象,所以共需要一次構造和一次拷貝構造。從效率上看不如emplace_back,因為后者只需要一次調用一次構造即可。
③一般傳入emplace_back的是構造函數所對應的參數(也只有這樣傳參才能節省一次拷貝構造),所以要求對象有相應的構造函數,如果沒有對應的構造函數,則只能用push_back,否則編譯會報錯。如emplace_back(int, int),則要求元素對象需要有帶兩個int型的構造函數。
【編程實驗】emplace_back減少內存拷貝和移動
#include <iostream> #include <vector> using namespace std; class Test { int m_a; public: static int m_count; Test(int a) : m_a(a) { cout <<"Test(int a)" << endl; } Test(const Test& t) : m_a(t.m_a) { ++m_count; cout << "Test(const Test& t)" << endl; } Test& operator=(const Test& t) { this->m_a = t.m_a; return *this; } }; int Test::m_count = 0; int main() { //創建10個值為1的元素 Test::m_count = 0; vector<Test> vec(10, 1); //首先將1轉為Test(1),會調用1次Test(int a)。然后,利用Test(1)去拷貝構造10個元素,所以 //調用10次拷貝構造。 cout << "vec.capacity():" << vec.capacity() << ", "; //10 cout << "vec.size():" << vec.size() << endl; //10,空間己滿 Test::m_count = 0; vec.push_back(Test(1)); //由於capacity空間己滿。首先調用Test(1),然后再push_back中再拷貝 //構造10個元素(而不是1個,為了效率),所以調用10次拷貝構造 cout << "vec.capacity():" << vec.capacity() << ", "; //20 cout << "vec.size():" << vec.size() << endl; //11,空間未滿 Test::m_count = 0; vec.push_back(1); //先調用Test(1),然后調用1次拷貝構造 cout << "vec.capacity():" << vec.capacity() << ", "; //20 cout << "vec.size():" << vec.size() << endl; //12,空間未滿 Test::m_count = 0; vec.emplace_back(1); //由於空間未滿,直接在第12個元素位置調用placement new初始化那段空間 //所以就會調用構造函數,節省了調用拷貝構造的開銷 cout << "vec.capacity():" << vec.capacity() << ", "; //20 cout << "vec.size():" << vec.size() << endl; //13,空間未滿 Test::m_count = 0; vec.emplace_back(Test(1)); //先調用Test(1),再調用拷貝構造(注意與vec.emplace_back(1)之間差異) cout << "vec.capacity():" << vec.capacity() << ", "; //20 cout << "vec.size():" << vec.size() << endl; //14,空間未滿 return 0; } /*輸出結果 e:\Study\C++11\16>g++ -std=c++11 test4.cpp e:\Study\C++11\16>a.exe Test(int a) ... //中間省略了調用10次Test(const Test& t) vec.capacity():10, vec.size():10 Test(int a) ... //中間省略了調用10次Test(const Test& t) vec.capacity():20, vec.size():11 Test(int a) Test(const Test& t) vec.capacity():20, vec.size():12 Test(int a) vec.capacity():20, vec.size():13 Test(int a) Test(const Test& t) vec.capacity():20, vec.size():14 */