Effective Modern C++ - 5: 右值引用/移动语义/完美转发

移动语义

(1) 替换 高代价的 copy

(2) 支持 创建只允许 move 的类型: std::unique_ptr/std::future/and std::thread

完美转发

任意参数(左值/右值)函数模板, 保持参数的左/右值特性, 转发给另一函数

形参 总是左值, 即使它的类型是 右值引用

    void f(Widget&& w);

item23 std::move 和 std::forward

std::move 不移动任何东西, forward 不转发任何内容, 两者都是 强转

(1) std::move 将其 实参强转为右值 -> 更好的名字 rvaluecast

(2) std::forward 仅在实参绑定到右值执行此强转

(3) 运行时 两者什么也不做

non-const 右值 是 移动的候选者, 将 std::move 应用于对象, 告诉编译器 可以对该对象进行移动

1 std::move 实现

接近标准
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 导致后续 copy 语义 的 case

从形参到成员数据, 对 左值 const 对象, std::move/rvaluecast 的结果右值 const 对象 -> 再作 arg 构造成员数据, 调 copy cotr 而非 move ctor

原因: 右值 const 对象, 无法被编译器移动

move Ctor/Assignment 参数: 对 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 实现

(1) move 只需要1个函数实参(如 rhs.s) => 更方便、更不易出错

(2) forward 还需要 模板实参类型(如 std::string); 且 模板实参类型错误时, 将导致行为不是本意

本例, std::forward<T>(arg) 的 模板实参(即, T 的具体类型) 不应该显式带 引用(即 std::string&), 才能在 函数实参 绑定到右值时, 将实参转换为右值, 导致后续的 move 语义;

显式带 引用(即 std::string&), 将函数实参转换为左值 -> 导致后续 copy 语义

=>

[1] std::forward 通常应该置于 转发函数模板 fwd() 内, 并保持 std::forward 模板 和 转发函数模板 的 模板形参 T 相同, 才能保证 按实参的 左右值特性进行转发

[2] std::forward 单独使用时, 其 函数实参 是 (Widget 的) 左/右值 时, 模板实参 T 必须为 引用/非引用 (Widget& / Widget) 才能保证 按实参的 左右值特性进行转发

class Widget 
{
public:
    Widget(Widget&& rhs)
        : s( std::move(rhs.s) )
    { ++moveCtorCalls; }
 // ...
private:
    static std::size_t moveCtorCalls;
    std::string s;
};

rhs 绑定到 右值 => forward 的函数实参 rhs.s 也绑定到右值

class Widget {
public:
    Widget(Widget&& rhs)                        // unconventional,
        : s(std::forward<std::string>(rhs.s) )  // undesirable
    { ++moveCtorCalls; }                        // implementation
    …
};

forward 转发右值, 若 模板实参却是 引用 => forward 将 右值转发成左值(行为错误) => 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 区分 万能引用 和 右值引用

万能引用: 能绑定到 左值 右值 const non/const volatile/Non-volatile

右值引用: 只能绑定到右值

万能引用表面抽象, 底层机制引用折叠(item28)

1 区分: && 左侧整体是否可以看成未定类型 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 发生模板推断是万能引用的必要非充分条件

模板推断, 且推断的是&&左侧整体 才是万能引用的充要条件

(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) 容器的 非模板成员函数: 实例化后 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) 容器的模板成员函数 emplace_back(): 模板函数的 模板形参 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 转发

右值引用应该无条件地强转为右值(通过 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:

出乎意料修改左值(如 左值) -> 左值后续还要用, 却因为被移动而变为空(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) 软件设计问题: 参数变多 或 参数数量不限时, 每个参数都可能是左值或右值 -> n 参数, 2^n 组合

解决: 万能引用

4 对象多次使用 => 最后1次使用时, 才应该用 std::move(对 rvalue references)或std::forward(对 universal references), 以保证 参数值不会被改变

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) 对 局部变量 或 函数形参 return by value

[1] 编译器支持 RVO(返回值优化), 且 发生 RVO 时, 最高效

2个条件

1] 返回值类型local object(不算 函数形参) 类型相同

2] 返回的对象 正是 local object, 而非 被 std::move 转化成的 右值(引用)

RVO: 编译器 把 local object 分配/构造 在 函数返回值的内存位置上, 而非 本函数栈帧内

    => 比 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 语义

函数形参 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 数据结构中;

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 数据结构, 对 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 优化

对 case2/3, 可 move 进 global 数据结构 / 直接对 C 风格字符串 在 global 数据结构 中 就地构造 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 -> 重载 万能引用 的 普通模板函数

(1) 只能通过1个中间层查询函数, 查表得 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) 与 查询函数形参类型(如 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匹配得一样好, 但优先普通函数而非实例化版本 => 编译报错

(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 万能引用: 放弃重载(标签分发 最高效) 或 限制重载

1 放弃重载

(1) Pass by const T&

[1] 普通模板函数 用不同名字: 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) 标签分发: 结合 universal references 与 重载; client 用1个 API

普通模板函数 内部调 实际做事的模板, 新增1个 编译期参数, 据 模板形参类型的信息选择2种不同的版本

其中1种版本调用 原函数模板 -> 调 另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, 标签分发不能解决: 因为可能优选 编译器默认生成的 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 保持左右值特性进行转发

3 client不允许写 引用的引用(编译时会报错); 编译器自己在类型推断后若发现结果是引用的引用, 会将其引用折叠成单个引用

简单规则: 两个引用, 合起来 超过2个引用符号(&&)时, 消掉两个引用符号

        T       T&&
    Widget      Widget&&
    Widget&     Widget&
    Widget&&    Widget&&    

4 万能引用

(1) 本质上(术语上)可以视为 右值引用, 只是发生在 类型推断能区分出左值和右值发生 引用折叠上下文

(2) 应用中, 按 类型推断 + 引用折叠 去分析, 更方便、更易理解

5 4种引用折叠的上下文

    模板实例化 / auto 类型生成 / typedef 的创建和使用, 及 别名声明 / 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 完美转发

    template<typename T>
    void f(T&& fParam)
    {
         … // do some work
         someFunc(std::forward<T>(fParam)); // forward fParam to
    }

(1) 转发 左值 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) 转发 右值 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 实现(非完全标准)

    // 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、不能用、不会发生

1 不存在

(1) C++11 前

(2) C++11 编译器, 但编译器不支持 移动语义

(3) C++11, 但 编译器默认不生成 move Ctor/Assignment

[1] 声明了 copy 操作 / move 操作 / Dtor

[2] 数据成员 或 基类 disable move 操作, 例如通过 delete 操作

2 不 cheap

C++11 所有容器都支持移动, 但并非 移动所有容器 都很便宜

(1) std::array 本质是 带有STL接口的内置数组

假定 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 短字符串优化: 不用堆存储, 而是存储在 string 对象内的缓冲区 -> 效率高

3 不能用

一些 context 中, 移动语义的发生要求移动操作不会抛出异常, 但移动操作 未声明为 noexcept, 则编译器会执行相应的 copy 操作

4 不会发生: 源对象 是左值

原因: 仅右值 可能被当作移动操作的 源

5 移动操作 cheap 的 case: 仅 copy 指向容器内容的 指针, 并令原指针为 空 -> 常数时间

    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 完美转发失败的 cases

1 完美转发基本形式

    template<typename T>
    void 
    fwd(T&& param)                  // accept any argument
    {
        f(std::forward<T>(param) ); // forward it to f
    }

2 扩展: 可变参数模板: 任意数量的实参

    template<typename... Ts>
    void 
    fwd(Ts&&... params)              // accept any arguments
    {
        f(std::forward<Ts>(params)... ); // forward them to f
    }
(1) 容器 emplacement 函数 

(2) 智能指针 工厂函数 
    
    std::make_shared  std::make_unique

3 完美转发失败 的 case

若 用1个特定实参 调用 目标函数 f 做1件事, 但 用同样的实参 调用 转发函数fwd 做不同的事 -> 则完美转发失败

    f( expression );    // if this does one thing,
    fwd( expression );  // but this does something else, fwd fails
                        // to perfectly forward expression to f

(1) 花括号的初始化器 + 调 函数模板 -> 编译器报错

    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

=> 花括号的初始化器 想传递给 函数模板 => 用 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都 不能 完美转发为空指针

解决: 传递 nullptr 而不是 0 或 NULL

(3) 只声明但没定义static const 成员数据

CPU 看来, 传引用 和 传指针一样 => static const 成员数据 只声明不定义, 则无法取地址

    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

解决: 实现文件中 定义 即可

    const std::size_t Widget::MinVals; // in Widget's .cpp file

(4) 重载 + 转发函数指针

(1) 普通函数指针

    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) 模板函数指针

    template<typename T>
    T 
    workOnVal(T param) // template for processing values
    { … }
    
    fwd(workOnVal); // error! which workOnVal instantiation?

解决: 函数指针类型 别名 + 转发函数 (模板函数指针)实参 静态强转为指定 函数指针类型 static_cast<ProcessFuncType> , 后传递给转发函数

    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份(静态强转) 转发

    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
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容