移动语义
(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);
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