移動語義
(1) 替換 高代價的 copy
(2) 支持 創(chuàng)建只允許 move 的類型: std::unique_ptr/std::future/and std::thread
完美轉發(fā)
帶 任意參數(shù)(左值/右值)
的函數(shù)模板
, 保持參數(shù)的左/右值特性, 轉發(fā)給另一函數(shù)
形參 總是左值
, 即使它的類型是 右值引用
void f(Widget&& w);
item23 std::move 和 std::forward
std::move 不移動
任何東西, forward 不轉發(fā)
任何內容, 兩者都是 強轉
(1) std::move 將其 實參強轉為右值
-> 更好的名字 rvaluecast
(2) std::forward 僅在實參綁定到右值
時 執(zhí)行此強轉
(3) 運行時 兩者什么也不做
non-const 右值 是 移動的候選者
, 將 std::move 應用于對象, 告訴編譯器 可以對該對象進行移動
1 std::move 實現(xiàn)
接近標準
template<typename T> // in namespace std
typename remove_reference<T>::type&&
move(T&& param)
{
using ReturnType =
typename remove_reference<T>::type&&;
return static_cast<ReturnType>(param);
}
template<typename T> // C++14; still in namespace std
decltype(auto)
move(T&& param)
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}
2 std::move 應用
(1) pass by value
class Annotation
{
public:
explicit Annotation(std::string text); // param to be copied,
// ...
};
(2) 只讀
: const 修飾
class Annotation {
public:
explicit Annotation(const std::string text)
// …
};
(3) std::move 導致后續(xù) copy 語義
的 case
從形參到成員數(shù)據(jù), 對 左值 const 對象
, std::move/rvaluecast 的結果
是 右值 const 對象
-> 再作 arg 構造
成員數(shù)據(jù), 調 copy cotr
而非 move ctor
原因: 右值 const 對象, 無法被編譯器移動
move Ctor/Assignment
參數(shù): 對 non-const 對象的 右值引用
class Annotation
{
public:
explicit
Annotation(const std::string text)
: value(std::move(text) ) // "move" text into value; this code
{ … } // doesn't do what it seems to!
…
private:
std::string value;
};
class string { // std::string is actually a typedef for std::basic_string<char>
public:
…
string(const string& rhs); // copy ctor
string(string&& rhs); // move ctor
…
};
3 std::std::forward 應用
void process(const Widget& lvalArg); // process lvalues
void process(Widget&& rvalArg); // process rvalues
template<typename T> // template that passes param to process
void logAndProcess(T&& param)
{
auto now =
std::chrono::system_clock::now();
makeLogEntry("Calling 'process'", now);
process(std::forward<T>(param) );
}
Widget w;
logAndProcess(w); // call with lvalue
logAndProcess(std::move(w) ); // call with rvalue
4 同一功能分別用 move 和 forward 實現(xiàn)
(1) move 只需要1個函數(shù)實參
(如 rhs.s) => 更方便、更不易出錯
(2) forward 還需要 模板實參類型
(如 std::string); 且 模板實參類型錯誤時, 將導致行為不是本意
本例, std::forward<T>(arg) 的 模板實參
(即, T 的具體類型) 不應該顯式帶 引用(即 std::string&)
, 才能在 函數(shù)實參 綁定到右值時, 將實參轉換為右值
, 導致后續(xù)的 move 語義
;
若顯式帶 引用(即 std::string&)
, 將函數(shù)實參轉換為左值
-> 導致后續(xù) copy 語義
=>
[1] std::forward 通常應該置于 轉發(fā)函數(shù)模板 fwd() 內, 并保持 std::forward 模板 和 轉發(fā)函數(shù)模板 的 模板形參 T 相同
, 才能保證 按實參的 左右值特性進行轉發(fā)
[2] std::forward 單獨使用
時, 其 函數(shù)實參 是 (Widget 的) 左/右值
時, 模板實參 T 必須為 引用/非引用 (Widget& / Widget)
才能保證 按實參的 左右值特性進行轉發(fā)
class Widget
{
public:
Widget(Widget&& rhs)
: s( std::move(rhs.s) )
{ ++moveCtorCalls; }
// ...
private:
static std::size_t moveCtorCalls;
std::string s;
};
rhs 綁定到 右值
=> forward 的函數(shù)實參 rhs.s 也綁定到右值
class Widget {
public:
Widget(Widget&& rhs) // unconventional,
: s(std::forward<std::string>(rhs.s) ) // undesirable
{ ++moveCtorCalls; } // implementation
…
};
forward 轉發(fā)右值
, 若 模板實參卻是 引用
=> forward 將 右值轉發(fā)成左值
(行為錯誤) => copy 到 s
class Widget {
public:
Widget(Widget&& rhs)
: s(std::forward<std::string&>(rhs.s) )
{ ++moveCtorCalls; }
…
};
// rhs 是右值 => rhs.s 是右值
std::forward<std::string&>(rhs.s)
template<typename T> // in namespace std
T&&
forward(typename remove_reference<T>::type& param)
{
return static_cast<T&&>(param);
}
std::string& &&
forward(typename remove_reference<std::string&>::type& param)
{
return static_cast<std::string& &&>(param);
}
std::string&
forward(std::string& param)
{
return static_cast<std::string&>(param);
}
item24 區(qū)分 萬能引用 和 右值引用
萬能引用: 能綁定到 左值 右值 const non/const volatile/Non-volatile
右值引用: 只能綁定到右值
萬能引用
是表面抽象
, 底層機制
是 引用折疊
(item28)
1 區(qū)分: && 左側整體
是否可以看成未定類型 T
或 未定類型占位符 auto
void f(Widget&& param); // rvalue reference
Widget&& var1 = Widget(); // rvalue reference
template<typename T>
void f(std::vector<T>&& param); // rvalue reference
template<typename T>
void f(T&& param); // not rvalue reference
auto&& var2 = var1; // not rvalue reference
2 初值 的左/右值特性
決定萬能引用折疊成左值引用還是右值引用
template<typename T>
void f(T&& param); // param is a universal reference
Widget w;
f(w); // lvalue passed to f; param's type is
// Widget& (i.e., an lvalue reference)
f(std::move(w) ); // rvalue passed to f; param's type is
// Widget&& (i.e., an rvalue reference)
3 發(fā)生模板推斷
是萬能引用的必要非充分條件
模板推斷, 且推斷的是&&左側整體
才是萬能引用的充要條件
(1) 元素類型未定的容器
作 paramType 的一部分: 調用時 T 確定
// 推斷的是&&左側的一部分
template<typename T>
void f(std::vector<T>&& param);//rvalue reference
(2) const 修飾
template<typename T>
void f(const T&& param); // param is an rvalue reference
(3) 容器的 非模板成員函數(shù)
: 實例化后 T 確定
template<class T, class Allocator = allocator<T>> // from C++ / Standards
class vector { /
public:
void push_back(T&& x);
…
};
std::vector<Widget> v;
class vector<Widget, allocator<Widget>> {
public:
void push_back(Widget&& x); // rvalue reference
…
};
(4) 容器的模板成員函數(shù) emplace_back(): 模板函數(shù)的 模板形參 Args 與 容器的模板形參 T 獨立
-> 是萬能引用
template<class T, class Allocator = allocator<T> >
class vector
{
public:
template <class... Args>
void emplace_back(Args&&... args);
…
};
4 func 綁定到 可調用對象
auto timeFuncInvocation =
[](auto&& func, auto&&... params) // C++14
{
start timer;
std::forward<decltype(func)>(func)( // invoke func on params
std::forward<decltype(params)>(params)... );
stop timer and record elapsed time;
};
item25 在右值引用
上用 std::move
, 在萬能引用
上用 std::forward
右值引用形參
=> 綁定的對象
才可能被移動
1 轉發(fā)
時
右值引用
應該無條件地強轉為右值(通過 std::move)
, 因為它們總是 綁定到右值
; 萬能引用
應該有條件地強轉為右值(通過 std::forward)
, 因為它們只在某些時候 綁定到右值
(1)
class Widget
{
Widget(Widget&& rhs); // rhs definitely refers to an
… // object eligible for moving
};
class Widget
{
public:
Widget(Widget&& rhs) // rhs is rvalue reference
: name(std::move(rhs.name) ),
p(std::move(rhs.p) )
{ … }
…
private:
std::string name;
std::shared_ptr<SomeDataStructure> p;
};
(2)
class Widget
{
public:
template<typename T>
void setName(T&& newName) // newName is universal reference
{
name = std::forward<T>(newName);
}
…
};
2 右值引用
應該避免用 std::forward
-> 啰嗦、易錯;
萬能引用應該避免用 std::move
:
出乎意料
地修改左值(如 左值)
-> 左值后續(xù)還要用
, 卻因為被移動
而變?yōu)?code>空(null)
class Widget {
public:
template<typename T>
void setName(T&& newName) // universal reference
{ name = std::move(newName); } // compiles, but is bad, bad, bad!
…
private:
std::string name;
std::shared_ptr<SomeDataStructure> p;
};
std::string getWidgetName(); // factory function
Widget w;
auto lWidget = getWidgetName(); // n is local variable
w.setName(lWidget); // moves n into w!
… // n's value now unknown
3 用 一對 const 左值引用 + 右值引用
(配合移動) 代替 萬能引用
-> 能工作, 但有2大缺點
class Widget
{
public:
void setName(const std::string& newName) // set from const lvalue
{ name = newName; }
void setName(std::string&& newName) // set from rvalue
{ name = std::move(newName); }
…
};
(1) 運行時開銷
實參 字符串字面值
傳給 形參std::string
的引用 時, 要生成1個 string 臨時對象
, 再 move into
-> 效率低
但萬能引用: 直接
用 const char*
指針 作 實參
, 調 std::string 的 賦值運算符 operator=
推廣: 并非所有類型
的 move 操作
都是 cheap
的
(2) 軟件設計問題: 參數(shù)變多 或 參數(shù)數(shù)量不限
時, 每個參數(shù)都可能是左值或右值 -> n 參數(shù), 2^n 組合
解決: 萬能引用
4 對象多次使用 => 最后1次使用
時, 才應該用 std::move
(對 rvalue references)或std::forward
(對 universal references), 以保證 參數(shù)值不會被改變
template<typename T> // text is
void setSignText(T&& text) // univ. reference
{
sign.setText(text); // use text, but
// don't modify it
auto now =
std::chrono::system_clock::now();
signHistory.add(now,
std::forward<T>(text) ); // conditionally cast
}
5 return by value
(1) 對 (綁定到外部實參的)右值引用 或 萬能引用
return by value -> 用 std::forward 或 std::move 返回該引用
-> 比不用可能更高效
[1] copy
Matrix // by-value return
operator+(Matrix&& lhs, const Matrix& rhs)
{
lhs += rhs;
return lhs; // lhs 是左值 => copy lhs into
}
[2] 能 move 則 move; 不能 則 copy
Matrix // by-value return
operator+(Matrix&& lhs, const Matrix& rhs)
{
lhs += rhs;
return std::move(lhs); // move lhs into => 更高效
}
template<typename T>
Fraction // by-value return
reduceAndCopy(T&& frac) // universal reference param
{
frac.reduce();
return std::forward<T>(frac); // move rvalue into return value, copy lvalue
}
(2) 對 局部變量 或 函數(shù)形參
return by value
[1] 編譯器支持 RVO(返回值優(yōu)化), 且 發(fā)生 RVO
時, 最高效
2個條件
1] 返回值類型
與 local object
(不算 函數(shù)形參) 類型相同
2] 返回的對象 正是 local object
, 而非 被 std::move 轉化成的 右值(引用)
RVO: 編譯器 把 local object 分配/構造 在 函數(shù)返回值的內存位置上
, 而非 本函數(shù)棧幀內
=> 比 move 高效
// RVO
Widget
makeWidget() // "Copying" version of makeWidget
{
Widget w; // local variable
… // configure w
return w; // "copy" w into return value
}
[2] 顯式 std::move
或 被編譯器轉化為 std::move
時, move 語義
函數(shù)形參 return by value
會被編譯器轉化為 std::move 后, 再 return by value
[1] 顯式 move
Widget
makeWidget()
{
Widget w;
…
return std::move(w); // treat w as rvalue, because no copy elision was performed
}
[2] 隱式 move
Widget
makeWidget(Widget w) // by-value parameter of same
{ // type as function's return
…
return w;
}
// 被編譯器轉化為
Widget
makeWidget(Widget w)
{
…
return std::move(w); // treat w as rvalue
}
item26 避免重載
萬能引用
1 std::string 形參
, 通過 emplace 加到 global 數(shù)據(jù)結構中
;
3種 實參: 左值 string / 右值 string / C 風格字符串
std::multiset<std::string> names; // global data structure
void logAndAdd(const std::string& name)
{
auto now =
std::chrono::system_clock::now();
log(now, "logAndAdd"); // make log entry
names.emplace(name); // add name to global data
} // structure; see Item 42 for info on emplace
形參是 左值
=> 3種 case 下, 形參 被 emplace() copy 進 global 數(shù)據(jù)結構
, 對 case2/3 效率低
std::string petName("Darla");
// (1) pass lvalue std::string => petName 是左值, 傳給 左值 name, name 被 emplace() copy 進 names
logAndAdd(petName);
// (2) pass rvalue std::string => 右臨時對象 是右值, 但傳給 左值 name, ...
logAndAdd(std::string("Persephone"));
// (3) pass string literal: `生成 右值臨時對象`是右值, 但傳給 左值 name, ...
logAndAdd("Patty Dog");
2 優(yōu)化
對 case2/3, 可 move 進 global 數(shù)據(jù)結構
/ 直接對 C 風格字符串
在 global 數(shù)據(jù)結構 中 就地構造 string
template<typename T>
void
logAndAdd(T&& name)
{
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(std::forward<T>(name) );
}
std::string petName("Darla"); // as before
logAndAdd(petName); // as before, copy lvalue into multiset
logAndAdd(std::string("Persephone") ); // move rvalue instead of copying it
logAndAdd("Patty Dog"); // create std::string in multiset
// instead of copying a temporary std::string
3 若 client 不能直接訪問 string
-> 重載 萬能引用 的 普通模板函數(shù)
(1) 只能通過1個中間層
的查詢函數(shù)
, 查表得 string
std::string nameFromIdx(int idx); // return name corresponding to idx
void
logAndAdd(int idx) // new overload
{
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(nameFromIdx(idx));
}
std::string petName("Darla"); // as before
logAndAdd(petName); // as before, these
logAndAdd(std::string("Persephone")); // calls all invoke
logAndAdd("Patty Dog"); // the T&& overload
logAndAdd(22); // calls int overload
(2) 若 實參類型(如 short) 與 查詢函數(shù)形參類型(如 int)不同
-> 萬能引用 模板
比 其重載版本 匹配性好
=> 但 編譯不過
: 沒有 short 到 string 的 Ctor
short nameIdx;
… // give nameIdx a value
logAndAdd(nameIdx); // error!
=> 重載 萬能引用的模板 是個壞主意
4 重載 萬能引用的模板 Ctor
-> 編譯器還會生成 默認 Copy Ctor / Move Ctor
(1) 若 萬能引用的模板 Ctor
是更好的匹配
-> 實例化 -> 再編譯: 沒有從 string 到 (實例化)類的 Ctor -> 編譯報錯
(2) 若 編譯器還會生成的 默認 Copy Ctor
是匹配得一樣好
, 但優(yōu)先普通函數(shù)而非實例化版本 => 編譯報錯
(3) 繼承
: Derived 的 copy/move Ctor 調用的不是 Base 的 copy/move Ctor, 而是 Base 的 forwarding Ctor
問題的解決: item27
(1)
class Person {
public:
template<typename T>
explicit Person(T&& n) // perfect forwarding ctor;
: name(std::forward<T>(n) ) {} // initializes data member
explicit Person(int idx) // int ctor
: name(nameFromIdx(idx) ) {}
…
private:
std::string name;
};
class Person {
public:
template<typename T> // perfect forwarding ctor
explicit Person(T&& n)
: name(std::forward<T>(n) ) {}
explicit Person(int idx); // int ctor
Person(const Person& rhs); // copy ctor: compiler-generated
Person(Person&& rhs); // move ctor: compiler-generated
…
};
Person p("Nancy");
auto cloneOfP(p); // create new Person from p;
// this won't compile!
class Person
{
public:
explicit Person(Person& n) // instantiated from
: name(std::forward<Person&>(n)) {} // perfect-forwarding template
explicit Person(int idx); // as before
Person(const Person& rhs); // copy ctor: compiler-generated
… //
};
(2)
const Person cp("Nancy"); // object is now const
auto cloneOfP(cp); // calls copy constructor!
// 實例化
class Person
{
public:
explicit
Person(const Person& n); // instantiated from template
Person(const Person& rhs);
…
};
(3)
class SpecialPerson: public Person
{
public:
// copy ctor; calls base class forwarding ctor!
SpecialPerson(const SpecialPerson& rhs)
: Person(rhs)
{ … }
// move ctor; calls base class forwarding ctor!
SpecialPerson(SpecialPerson&& rhs)
: Person(std::move(rhs) )
{ … }
};
item27 萬能引用: 放棄重載(標簽分發(fā) 最高效) 或 限制重載
1 放棄重載
(1) Pass by const T&
[1] 普通模板函數(shù) 用不同名字: logAndAddName 和 logAndAddNameIdx
[2] 模板 Ctor: 名字固定, 不能解決
上述問題
(2) Pass by value
class Person
{
public:
explicit Person(std::string n) // replaces T&& ctor; see
: name(std::move(n) ) {} // Item 41 for use of std::move
explicit Person(int idx) // as before
: name(nameFromIdx(idx) ) {}
…
private:
std::string name;
};
(3) 標簽分發(fā): 結合 universal references 與 重載; client 用1個 API
普通模板函數(shù) 內部調 實際做事的模板
, 新增1個 編譯期參數(shù)
, 據(jù) 模板形參類型的信息選擇
調 2種不同的版本
其中1種版本
再調用 原函數(shù)模板
-> 調 另1種版本
template<typename T>
void logAndAdd(T&& name)
{
logAndAddImpl(
std::forward<T>(name),
std::is_integral<typename std::remove_reference<T>::type>() );
}
template<typename T> // non-integral
void logAndAddImpl(T&& name, std::false_type) // argument: add it to
{
auto now = std::chrono::system_clock::now(); // global data
log(now, "logAndAdd"); // structure
names.emplace(std::forward<T>(name) );
}
std::string nameFromIdx(int idx); // as in Item 26
// integral argument: look up name and call logAndAdd with it
void logAndAddImpl(int idx, std::true_type)
{
logAndAdd(nameFromIdx(idx) );
}
2 限制
(帶萬能引用的)重載
對模板 Ctor, 標簽分發(fā)不能解決: 因為可能優(yōu)選 編譯器默認生成的 Copy Ctor / Move Ctor
解決: 編譯期, 通過條件 限制是否使能
模板 Ctor 的 實例化
(1) std::enable_if 條件滿足
( 傳遞的類型 不是 Person) 時, 才使能
模板 Ctor 的 實例化
(2) 編譯期檢查
由實參是否可以構造出
類的成員
Person p("Nancy"); // 提供1個
auto cloneOfP(p); // initialize from lvalue
class Person {
public:
template<
typename T,
typename = std::enable_if_t<
!std::is_base_of<Person, std::decay_t<T> >::value
&&
!std::is_integral<std::remove_reference_t<T> >::value
>
>
explicit Person(T&& n) // ctor for std::strings and args convertible to std::strings
: name(std::forward<T>(n) )
{
// assert that a std::string can be created from a T object
static_assert(
std::is_constructible<std::string, T>::value,
"Parameter n can't be used to construct a std::string"
);
… // the usual ctor work goes here
}
explicit Person(int idx) // ctor for integral args
: name(nameFromIdx(idx) )
{ … }
… // copy and move ctors, etc.
private:
std::string name;
};
item28 引用折疊
1 從定義上說, 左值引用是左值, 右值引用是右值
2 左/右值 實參
傳遞給 std::forward
, 返回 左值引用(左值)/右值引用(右值)
=> std::forward 保持左右值特性
進行轉發(fā)
3 client
不允許寫 引用的引用
(編譯時會報錯); 編譯器
自己在類型推斷
后若發(fā)現(xiàn)結果是引用的引用
, 會將其引用折疊
成單個引用
簡單規(guī)則: 兩個引用, 合起來 超過2個引用符號(&&)
時, 消掉兩個
引用符號
T T&&
Widget Widget&&
Widget& Widget&
Widget&& Widget&&
4 萬能引用
(1) 本質上(術語上)可以視為 右值引用
, 只是發(fā)生在 類型推斷能區(qū)分出左值和右值
且 發(fā)生 引用折疊
的上下文
中
(2) 應用中, 按 類型推斷 + 引用折疊
去分析, 更方便、更易理解
5 4種引用折疊的上下文
模板實例化 / auto 類型生成 / typedef 的創(chuàng)建和使用, 及 別名聲明 / decltype
展開
3
3.1 引用的引用
(1) 用戶寫 引用的引用 => 編譯報錯
int x;
…
auto& & rx = x; // declare reference to reference => error!
(2) 編譯器看到
引用的引用 -> 引用折疊
成單引用
template<typename T>
void func(T&& param); // as before
func(w); // invoke func with lvalue; T deduced as Widget&
// 編譯器看到推斷的結果是 引用的引用
void func(Widget& && param);
// 編譯器進行 引用折疊, 結果為
void func(Widget& param);
3.2 完美轉發(fā)
template<typename T>
void f(T&& fParam)
{
… // do some work
someFunc(std::forward<T>(fParam)); // forward fParam to
}
(1) 轉發(fā) 左值
Widget: forward 的模板實參
T 推斷為 Widget&
void f(Widget& fParam)
{
…
someFunc(std::forward<Widget&>(fParam) ); // forward fParam to
}
// 編譯器看到
Widget& &&
forward(typename remove_reference<Widget&>::type& param)
{
return static_cast<Widget& &&>(param);
}
Widget&
forward(Widget& param)
{
return static_cast<Widget&>(param);
}
(2) 轉發(fā) 右值
Widget: forward 的模板實參
T 推斷為 Widget
void f(Widget&& fParam)
{
…
someFunc(std::forward<Widget>(fParam) ); // forward fParam to
}
// 編譯器看到
Widget&&
forward(typename remove_reference<Widget>::type& param)
{
return static_cast<Widget&&>(param);
}
// 編譯器引用折疊生成
Widget&&
forward(Widget& param)
{
return static_cast<Widget&&>(param);
}
3.3 std::forward 實現(xiàn)(非完全標準)
// C++11
template<typename T> // in namespace std
T&&
forward(typename remove_reference<T>::type& param)
{
return static_cast<T&&>(param);
}
// C++14
template<typename T> // C++14; still in namespace std
T&&
forward(remove_reference_t<T>& param)
{
return static_cast<T&&>(param);
}
3.4 std::forward 傳遞 左/右值
-> 返回 左值引用(左值) / 右值引用(右值)
[1] std::forward 傳遞 左值
Widget
// 編譯器看到
Widget& &&
forward(typename remove_reference<Widget&>::type& param)
{
return static_cast<Widget& &&>(param);
}
// 編譯器引用折疊生成
Widget&
forward(Widget& param) // still in namespace std
{
return static_cast<Widget&>(param);
}
[2] std::forward 傳遞 右值
Widget
// 編譯器看到
Widget&&
forward(typename remove_reference<Widget>::type& param)
{
return static_cast<Widget&&>(param);
}
// 編譯器引用折疊生成
Widget&&
forward(Widget& param)
{
return static_cast<Widget&&>(param);
}
3.5 兩種引用, 4種組合
references
lvalue
rvalue
reference-reference combinations
lvalue to lvalue
lvalue to rvalue
rvalue to lvalue
rvalue to rvalue
5
(1)
template<typename T>
void func(T&& param);
Widget w; // a variable (an lvalue)
Widget widgetFactory(); // function returning rvalue
func(w); // call func with lvalue; T deduced to be Widget&
func(widgetFactory()); // call func with rvalue; T deduced to be Widget
(2)
auto&& w1 = w;
// Widget& && w1 = w;
Widget& w1 = w;
auto&& w2 = widgetFactory();
Widget&& w2 = widgetFactory();
(3)
template<typename T>
class Widget {
public:
typedef T&& universalRefToT;
…
};
Widget<int&> w;
// 引用折疊后 int& && -> int&
typedef int& universalRefToT;
item29 假定 移動操作 不存在掌腰、不 cheap、不能用厢蒜、不會發(fā)生
1 不存在
(1) C++11 前
(2) C++11 編譯器, 但編譯器不支持 移動語義
(3) C++11, 但 編譯器默認不生成 move Ctor/Assignment
[1] 聲明了 copy 操作 / move 操作 / Dtor
[2] 數(shù)據(jù)成員 或 基類
disable move 操作, 例如通過 delete 操作
2 不 cheap
C++11 所有容器都支持移動, 但并非 移動所有容器 都很便宜
(1) std::array 本質是 帶有STL接口的內置數(shù)組
假定 Widget 的 move 比 copy 快
=> std::array<Widget, 10000> 的 move 要逐個 Widget move => 線性時間
std::array<Widget, 10000> aw1;
// put data into aw1
…
// move aw1 into aw2. Runs in linear time.
// All elements in aw1 are moved into aw2
auto aw2 = std::move(aw1);
(2) std::string 短字符串優(yōu)化
: 不用堆存儲, 而是存儲在 string 對象內的緩沖區(qū)
-> 效率高
3 不能用
一些 context 中, 移動語義的發(fā)生要求移動操作不會拋出異常
, 但移動操作 未聲明為 noexcept
, 則編譯器會執(zhí)行相應的 copy 操作
4 不會發(fā)生: 源對象 是左值
時
原因: 僅右值 可能被當作移動操作的 源
5 移動操作 cheap 的 case: 僅 copy 指向容器內容的 指針
, 并令原指針為 空
-> 常數(shù)時間
std::vector<Widget> vw1;
// put data into vw1
…
// move vw1 into vw2. Runs in constant time.
// Only ptrs in vw1 and vw2 are modified
auto vw2 = std::move(vw1);
item30 完美轉發(fā)失敗
的 cases
1 完美轉發(fā)基本形式
template<typename T>
void
fwd(T&& param) // accept any argument
{
f(std::forward<T>(param) ); // forward it to f
}
2 擴展: 可變參數(shù)模板: 任意數(shù)量的實參
template<typename... Ts>
void
fwd(Ts&&... params) // accept any arguments
{
f(std::forward<Ts>(params)... ); // forward them to f
}
(1) 容器 emplacement 函數(shù)
(2) 智能指針 工廠函數(shù)
std::make_shared std::make_unique
3 完美轉發(fā)失敗 的 case
若 用1個特定實參 調用 目標函數(shù) f 做1件事
, 但 用同樣的實參 調用 轉發(fā)函數(shù)fwd 做不同的事
-> 則完美轉發(fā)失敗
f( expression ); // if this does one thing,
fwd( expression ); // but this does something else, fwd fails
// to perfectly forward expression to f
(1) 花括號的初始化器 + 調 函數(shù)模板 -> 編譯器報錯
void f(const std::vector<int>& v);
f({ 1, 2, 3 }); // fine, "{1, 2, 3}" implicitly
// converted to std::vector<int>
fwd( { 1, 2, 3 } ); // error! doesn't compile
Note: 花括號的初始化器 + 初始化 auto 變量 -> 編譯器推斷出 auto 為 std::initializer_list
=> 花括號的初始化器 想傳遞給 函數(shù)模板 => 用 auto 變量中轉
auto il = { 1, 2, 3 }; // il's type deduced to be
// std::initializer_list<int>
fwd(il); // fine, perfect-forwards il to f
(2) 0 或 NULL 作為空指針
item8: 將 0或NULL 作空指針 傳遞給模板
時, 類型推導出 整型(通常是int)
, 結果 0和NULL都 不能 完美轉發(fā)為空指針
解決: 傳遞 nullptr
而不是 0 或 NULL
(3) 只聲明但沒定義
的 static const 成員數(shù)據(jù)
在CPU 看來, 傳引用 和 傳指針一樣
=> static const 成員數(shù)據(jù) 只聲明不定義, 則無法取地址
class Widget {
public:
static const std::size_t MinVals = 28; // MinVals' declaration
…
};
… // no defn. for MinVals
std::vector<int> widgetData;
widgetData.reserve(Widget::MinVals); // use of MinVals
void f(std::size_t val);
f(Widget::MinVals); // fine, treated as "f(28)"
fwd(Widget::MinVals); // error! shouldn't link
解決: 實現(xiàn)文件中 定義
即可
const std::size_t Widget::MinVals; // in Widget's .cpp file
(4) 重載 + 轉發(fā)函數(shù)指針
(1) 普通函數(shù)指針
void f(int (*pf)(int)); // pf = "processing function"
void f(int pf(int)); // declares same f as above
int processVal(int value);
int processVal(int value, int priority);
f(processVal); // fine
fwd(processVal); // error! which processVal?
(2) 模板函數(shù)指針
template<typename T>
T
workOnVal(T param) // template for processing values
{ … }
fwd(workOnVal); // error! which workOnVal instantiation?
解決: 函數(shù)指針類型 別名 + 轉發(fā)函數(shù) (模板函數(shù)指針)實參 靜態(tài)強轉為指定 函數(shù)指針類型
static_cast<ProcessFuncType> , 后傳遞給轉發(fā)函數(shù)
using ProcessFuncType = int (*)(int);
ProcessFuncType processValPtr = processVal; // specify needed signature for processVal
fwd(processValPtr); // fine
fwd(static_cast<ProcessFuncType>(workOnVal) ); // also fine
(5) 比特域
原因: non-const 引用不能綁定到 比特域
解決: copy 1份(靜態(tài)強轉) 轉發(fā)
struct IPv4Header {
std::uint32_t version:4,
IHL:4,
DSCP:6,
ECN:2,
totalLength:16;
…
};
void f(std::size_t sz); // function to call
IPv4Header h;
…
f(h.totalLength); // fine
fwd(h.totalLength); // error!
// copy bitfield value; see Item 6 for info on init. form
auto length = static_cast<std::uint16_t>(h.totalLength);
fwd(length); // forward the copy