Effective Modern C++

【Effective Modern C++(5)】右值引用、移动

2018-12-11  本文已影响31人  downdemo

23 理解std::move和std::forward

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);
}

// C++14
template<typename T> // still in namespace std
decltype(auto) move(T&& param)
{
    using ReturnType = remove_reference_t<T>&&;
    return static_cast<ReturnType>(param);
}
class A {
public:
    explicit A(const std::string text)
    : val(std::move(text)) {} // 将text移动到val
private:
    std::string val;
};
class string { // std::string is actually a typedef for std::basic_string<char>
public:
    …
    string(const string& rhs); // 拷贝构造函数:可以接受const右值
    string(string&& rhs); // 移动构造函数:只能接受non-const右值
    …
};
void process(const Widget& lvalArg); // 处理左值
void process(Widget&& rvalArg); // 处理右值

template<typename T>
void logAndProcess(T&& param)
{
    auto now = std::chrono::system_clock::now(); // 获取当前时间
    makeLogEntry("Calling 'process'", now);
    process(std::forward<T>(param));
}

Widget w;
logAndProcess(w); // 传入左值
logAndProcess(std::move(w)); // 传入右值
class Widget {
public:
    Widget(Widget&& rhs)
    : s(std::move(rhs.s))
    { ++moveCtorCalls; }
    …
private:
    static std::size_t moveCtorCalls;
    std::string s;
};

// 用std::forward替代std::move
class Widget {
public:
    Widget(Widget&& rhs)
    : s(std::forward<std::string>(rhs.s))
    { ++moveCtorCalls; }
    …
};

24 区分转发引用和右值引用

void f(Widget&& param); // 右值引用
Widget&& var1 = Widget(); // 右值引用
auto&& var2 = var1; // 转发引用
template<typename T>
void f(std::vector<T>&& param); // 右值引用
template<typename T>
void f(T&& param); // 转发引用
void f(Widget&& param); // 无类型推断:右值引用
Widget&& var1 = Widget(); // 无类型推断:右值引用
template<typename T>
void f(T&& param); // param是转发引用
Widget w;
f(w); // 传递左值,param是Widget&,一个左值引用
f(std::move(w)); // 传递右值,param是Widget&&,一个右值引用
template<typename T>
void f(std::vector<T>&& param); // param是右值引用
std::vector<int> v;
f(v); // 错误:不能将右值引用绑定到左值

template<typename T> // 即使多了const修饰也不行
void f(const T&& param); // param是右值引用
template<class T, class Allocator = allocator<T>>
class vector {
public:
    void push_back(T&& x);
    …
};
std::vector<Widget> v;
// 对应的std::vector实例化为
class vector<Widget, allocator<Widget>> {
public:
    void push_back(Widget&& x); // 右值引用
    …
};
template<class T, class Allocator = allocator<T>>
class vector {
public:
    template <class... Args>
    void emplace_back(Args&&... args); // 转发引用
    …
};
auto timeFuncInvocation =
    [](auto&& func, auto&&... params) // C++14
    {
        启动计时器;
        std::forward<decltype(func)>(func)(
            std::forward<decltype(params)>(params)...
        );
        停止计时器并计算时间;
    };

25 对右值引用使用std::move,对转发引用使用std::forward

class Widget {
public:
    Widget(Widget&& rhs) // rhs是右值引用,会绑定到可移动对象上
    : name(std::move(rhs.name)),
    p(std::move(rhs.p))
    { … }
    …
private:
    std::string name;
    std::shared_ptr<SomeDataStructure> p;
};
class Widget {
public:
    template<typename T>
    void setName(T&& newName) // newName是转发引用
    { name = std::forward<T>(newName); }
    …
};
class Widget {
public:
    template<typename T>
    void setName(T&& newName) // 转发引用
    { name = std::move(newName); } // 可编译,但很糟糕
    …
private:
    std::string name;
    std::shared_ptr<SomeDataStructure> p;
};

std::string getWidgetName(); // 工厂函数
Widget w;
auto n = getWidgetName(); // n是局部变量
w.setName(n); // 把n移入w,n值现在未知
…
class Widget {
public:
    void setName(const std::string& newName)
    { name = newName; }
    void setName(std::string&& newName)
    { name = std::move(newName); }
    …
};
w.setName("Adela Novak"); // 将构造一个std::string临时变量
// 如果是转发引用版本则会直接赋值
template<class T, class... Args> // C++11
shared_ptr<T> make_shared(Args&&... args);
template<class T, class... Args> // C++14
unique_ptr<T> make_unique(Args&&... args);
template<typename T> // text is
void setSignText(T&& text) // 转发引用
{
    sign.setText(text); // 使用但不修改text值
    auto now = std::chrono::system_clock::now();
    signHistory.add(now, std::forward<T>(text)); // 有条件的转换
}
Matrix // by-value return
operator+(Matrix&& lhs, const Matrix& rhs) // 加法左侧矩阵是个右值
{
    lhs += rhs;
    return std::move(lhs); // move lhs into return value
}
Matrix operator+(Matrix&& lhs, const Matrix& rhs)
{
    lhs += rhs;
    return lhs; // copy lhs into return value
}
template<typename T>
Fraction // by-value return
reduceAndCopy(T&& frac) // 转发引用
{
    frac.reduce();
    return std::forward<T>(frac); // move rvalue into return value, copy lvalue
}
Widget makeWidget() // "Copying" version of makeWidget
{
    Widget w; // 局部变量
    …
    return std::move(w); // 不要这样做
}

26 避免重载转发引用

std::multiset<std::string> v;

void f(const std::string& s)
{
    v.emplace(s);
}

std::string s("Harry");
f(s); // 传递左值,只能拷贝
f(std::string("Potter")); // 传递右值,拷贝,但有办法改成移动
f("Wizard"); // 传递字面值,拷贝,但有办法改成移动
std::multiset<std::string> v;

template<typename T>
void f(T&& s)
{
    v.emplace(std::forward<T>(s));
}

std::string s("Harry");
f(s); // 只能拷贝
f(std::string("Potter")); // 移动
f("Wizard"); // 在multiset中直接构造一个std::string对象,而非拷贝一个临时对象
std::string nameFromIdx(int n); // 返回索引对应的名字

void f(int n) // 新的重载函数
{
    v.emplace(nameFromIdx(n));
}
// 之前的调用依然会选择T&&重载版本
std::string s("Harry");
f(s); // 只能拷贝
f(std::string("Potter")); // 移动
f("Wizard"); // 在multiset中直接构造一个std::string对象,而非拷贝一个临时对象

f(22); // 调用int重载版本
short nameIdx;
f(nameIdx); // 调用T&&版本,导致错误
class A {
public:
    template<typename T> // 完美转发构造函数
    explicit A(T&& n)
    : s(std::forward<T>(n)) {}

    explicit A(int n) // int构造函数
    : s(nameFromIdx(n)) {}
private:
    std::string s;
};
class A {
public:
    template<typename T> // 完美转发构造函数
    explicit A(T&& n)
    : s(std::forward<T>(n)) {}

    explicit A(int n) // int构造函数
    : s(nameFromIdx(n)) {}

    A(const A& rhs); // 默认生成的拷贝构造函数
    A(A&& rhs); // 默认生成的移动构造函数
private:
    std::string s;
};
A a("Harry");
auto b(a); // 错误
class A {
public:
    explicit A(A& n) // 完美转发构造函数的实例化
    : s(std::forward<A&>(n)) {}

    explicit A(int n) // int构造函数
    : s(nameFromIdx(n)) {}

    A(const A& rhs); // 默认生成的拷贝构造函数
    A(A&& rhs); // 默认生成的移动构造函数
private:
    std::string s;
};

A a("Harry");
auto b(a); // 调用的是完美转发构造函数,实例化如上
class A {
public:
    explicit A(const A& n) // 完美转发构造函数的实例化
    : s(std::forward<A&>(n)) {}

    explicit A(int n) // int构造函数
    : s(nameFromIdx(n)) {}

    A(const A& rhs); // 默认生成的拷贝构造函数
    A(A&& rhs); // 默认生成的移动构造函数
private:
    std::string s;
};

const A a("Harry"); // 现在是const对象
auto b(a); // 调用拷贝构造函数
class A {
public:
    template<typename T> // 完美转发构造函数
    explicit A(T&& n)
    : s(std::forward<T>(n)) {}

    explicit A(int n) // int构造函数
    : s(nameFromIdx(n)) {}

    A(const A& rhs); // 默认生成的拷贝构造函数
    A(A&& rhs); // 默认生成的移动构造函数
private:
    std::string s;
};

class B : public A {
public:
    B(const B& rhs) // 拷贝构造函数
    : A(rhs) { ... } // 调用基类的完美转发构造函数而非拷贝构造函数,用B初始化std::string导致出错

    B(B&& rhs) // 移动构造函数
    : A(std::move(rhs)) { ... } // 调用基类的完美转发构造函数而非移动构造函数,同样出错
};

27 重载转发引用的替代方案

1. 放弃重载

2. Pass by const T&

3. Pass by value

class A {
public:
    explicit A(std::string n) // 替代转发引用
    : s(std::move(n)) {}

    explicit A(int n) // int构造函数
    : s(nameFromIdx(n)) {}
private:
    std::string s;
};

4. 使用标签分派

std::multiset<std::string> v;
// 原有的函数模板
template<typename T>
void f(T&& s)
{
    v.emplace(std::forward<T>(s));
}

// 加上标签分派的想法
template<typename T>
void f(T&& s)
{
    fImpl(std::forward<T>(s), std::is_integral<T>()); // 还不够正确
}
template<typename T>
void f(T&& s)
{
    fImpl(std::forward<T>(s),
    std::is_integral<typename std::remove_reference<T>::type>());
}

// C++14中可以写得简单一些
template<typename T>
void f(T&& s)
{
    fImpl(std::forward<T>(s),
    std::is_integral<std::remove_reference_t<T>>());
}
template<typename T>
void fImpl(T&& s, std::false_type)
{
    v.emplace(std::forward<T>(s));
}

str::string nameFromIdx(int n);

void fImpl(int n, std::true_type)
{
    f(nameFromIdx(n)); // 委托回原函数
}

5. 使用std::enable_if限制模板

class A {
public:
    template<typename T,
        typename = typename std::enable_if<condition>::type>
    explicit A(T&& n);
    …
};
A a("Harry");
auto b(a); // T被推断为A&
class A {
public:
    template<
        typename T,
        typename = typename std::enable_if<
            !std::is_same<A,
                typename std::decay<T>::type
            >::value // is_same<A, T>::value
        >::type // enable_if<T>::type
    >
    explicit A(T&& n);
    …
};
class B : public A {
public:
    B(const B& rhs) // 拷贝构造函数
    : A(rhs) { ... } // 调用基类的完美转发构造函数而非拷贝构造函数,用B初始化std::string导致出错

    B(B&& rhs) // 移动构造函数
    : A(std::move(rhs)) { ... } // 调用基类的完美转发构造函数而非移动构造函数,同样出错
};
class A {
public:
    template<
        typename T,
        typename = typename std::enable_if<
            !std::is_base_of<A,
                typename std::decay<T>::type
            >::value // is_base_of<A, T>::value
        >::type // enable_if<T>::type
    >
    explicit A(T&& n);
    …
};

// C++14中可以使用std::enable_if_t和decay_t简化代码
class A {
public:
    template<
        typename T,
        typename = std::enable_if_t<
            !std::is_base_of<A, typename std::decay_t<T>>::value
        >
    >
    explicit A(T&& n);
    …
};
class A {
public:
    template<
        typename T,
        typename = std::enable_if_t<
            !std::is_base_of<A, typename std::decay_t<T>>::value
            &&
            !std::is_integral<std::remove_reference_t<T>>::value
        >
    >
    explicit A(T&& n)
    : s(std::forward<T>(n)) {}

    explicit A(int n) // int构造函数
    : s(nameFromIdx(n)) {}

    … // 拷贝和移动构造函数等
private:
    std::string s;
};

6. 权衡

class A {
public:
    template<
        typename T,
        typename = std::enable_if_t<
            !std::is_base_of<A, typename std::decay_t<T>>::value
            &&
            !std::is_integral<std::remove_reference_t<T>>::value
        >
    >
    explicit A(T&& n)
    : s(std::forward<T>(n))
    {
        static_assert( // 断言可以从T类型对象构造一个std::string类型对象
            std::is_constructible<std::string, T>::value,
            "Parameter n can't be used to construct a std::string"
        );
    }
    ...
};

28 理解引用折叠

int x = 42;
auto& & rx = x; // 错误:不能声明引用的引用
template<typename T>
void f(T&& param);

A a; // 左值
f(a); // T为A&
void f(A& && param);
template<typename T>
void f(T&& param)
{
    someF(std::forward<T>(param));
}
// 不完整的实现
template<typename T>
T&& forward(typename remove_reference<T>::type& param)
{
    return static_cast<T&&>(param);
}

// C++14
template<typename T>
T&& forward(remove_reference_t<T>& param)
{
    return static_cast<T&&>(param);
}
A& && forward(typename remove_reference<A&>::type& param)
{
    return static_cast<A& &&>(param);
}

// 简化后
A& forward(A& param)
{
    return static_cast<A&>(param);
}
A&& forward(typename remove_reference<A>::type& param)
{
    return static_cast<A&&>(param);
}

// 简化后
A&& forward(A& param)
{
    return static_cast<A&&>(param);
}
A a;
auto&& a1 = a; // a是左值,auto被推断为A&,a1为A& &&,折叠为A&
auto&& a2 = makeA(); // 工厂函数,右值,auto被推断为A,a1为A&&
template<typename T>
class A {
public:
    typedef T&& RvalueRef;
    ...
};

A<int&> a;
typedef int& && RvalueRef;
// 简化后
typedef int& RvalueRef;

29 移动操作缺乏优势的情形:不存在、高成本、不可用

std::vector的移动操作示意图 std::array的移动操作示意图

30 完美转发的失败情形

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

// 接受任意多个参数的版本
template<typename... Ts>
void fwd(Ts&&... params) // accept any arguments
{
    f(std::forward<Ts>(params)...); // forward them to f
}
f( expression ); // 执行某操作
fwd( expression ); // 执行另一操作,则完美转发失败

Braced initializers

void f(const std::vector<int>& v);

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

f({ 1, 2, 3 }); // OK,{1, 2, 3}隐式转换为std::vector<int>
fwd({ 1, 2, 3 }); // 无法推断T,导致编译错误

// 解决方法是借用auto推断出std::initializer_list类型再转发
auto il = { 1, 2, 3 }; // il的类型被推断为std::initializer_list<int>
fwd(il); // OK

0 or NULL as null pointers

void f(bool) { cout << "1"; }
void f(int) { cout << "2"; }
void f(void*) { cout << "3"; }

int main()
{
    f(0); // 2
    f(NULL); // 2,也可能不通过编译
    f(nullptr); // 3
}

Declaration-only integral static const data members

class Widget {
public:
    static const std::size_t MinVals = 28; // 声明MinVals
    …
};
… // 未定义MinVals
std::vector<int> v;
v.reserve(Widget::MinVals); // 使用MinVals,编译器会直接用28替代
void f(std::size_t val);
f(Widget::MinVals); // OK,视为f(28)
fwd(Widget::MinVals); // 错误,fwd形参是转发引用,需要取址,无法链接
// file "widget.cpp"
const std::size_t Widget::MinVals; // 已经指定过初始化值,无需重复指定

Overloaded function names and template names

void f(int (*pf)(int));
void f(int pf(int)); // 和上述写法相同
int processVal(int value);
int processVal(int value, int priority);

f(processVal); // OK,选择第一个processVal
fwd(processVal); // error! which processVal?
template<typename T>
T workOnVal(T param) // template for processing values
{ … }

fwd(workOnVal); // error! which workOnVal instantiation?
using ProcessFuncType = int (*)(int);
ProcessFuncType processValPtr = processVal; // 指定需要的签名
fwd(processValPtr); // OK
fwd(static_cast<ProcessFuncType>(workOnVal)); // OK

Bitfields

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
auto length = static_cast<std::uint16_t>(h.totalLength);
fwd(length); // forward the copy
上一篇 下一篇

猜你喜欢

热点阅读