Effective Modern C++ - 5: 右值引用/移動語義/完美轉發(fā)

移動語義

(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);
移動操作 cheap 的 case.png

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
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者烹植。
  • 序言:七十年代末斑鸦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子草雕,更是在濱河造成了極大的恐慌巷屿,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墩虹,死亡現(xiàn)場離奇詭異嘱巾,居然都是意外死亡憨琳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門旬昭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篙螟,“玉大人,你說我怎么就攤上這事问拘∠胁粒” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵场梆,是天一觀的道長。 經常有香客問我纯路,道長或油,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任驰唬,我火速辦了婚禮顶岸,結果婚禮上,老公的妹妹穿的比我還像新娘叫编。我一直安慰自己辖佣,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布搓逾。 她就那樣靜靜地躺著卷谈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霞篡。 梳的紋絲不亂的頭發(fā)上世蔗,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音朗兵,去河邊找鬼污淋。 笑死,一個胖子當著我的面吹牛余掖,可吹牛的內容都是我干的寸爆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼盐欺,長吁一口氣:“原來是場噩夢啊……” “哼赁豆!你這毒婦竟也來了?” 一聲冷哼從身側響起找田,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤歌憨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后墩衙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體务嫡,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡甲抖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了心铃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片准谚。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖去扣,靈堂內的尸體忽然破棺而出柱衔,到底是詐尸還是另有隱情,我是刑警寧澤愉棱,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布唆铐,位于F島的核電站,受9級特大地震影響奔滑,放射性物質發(fā)生泄漏艾岂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一朋其、第九天 我趴在偏房一處隱蔽的房頂上張望王浴。 院中可真熱鬧,春花似錦梅猿、人聲如沸氓辣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钞啸。三九已至,卻和暖如春喇潘,著一層夾襖步出監(jiān)牢的瞬間爽撒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工响蓉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留硕勿,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓枫甲,卻偏偏與公主長得像源武,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子想幻,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容