/**
几个概念:
1,移动语义:使用移动操作替换复制操作,比如移动构造函数和移动赋值运算符替换复制构造函数和复制赋值运算符
移动语义使得创建只移动型别对象成为可能,这些型别包括 std::unique_ptr std::future和std::thread等
2,完美转发:使人们可以撰写接受任意实参的函数模板,并转发到其他函数,目标函数会接受到与转发函数所接受的完全相同的实参
3,右值引用:将1,2 联系起来的底层语言机制,使 1,2成为可能
*/
/**
std::move 并不进行任何移动,仅仅只执行强制型别转换,无条件地将实参强制转换成右值
std::forward也不进行任何转发,仅仅在特定条件满足时才执行同一个强制转换
两者在运行期间都无所作为,也不会生成任何可执行代码,连一个字节都不会生成
*/
//C++11 std::move的实现
//C++11 std::move的实现
template<typename T>
typename remove_reference<T>::type&& move(T&& param)
{
using ReturnType = typename remove_reference<T>::type&&;
//std::move 实质部分,强制型别转换
//其形参使指涉到一个对象的引用,返回的使指涉到同一个对象的引用
//就是将实参强制转换成了右值
return static_cast<ReturnType>(param);
}
//C++14 std::move简明扼要的方式实现
//C++14 std::move简明扼要的方式实现
template<typename T>
decltype(auto) move(T&& param)
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}
//看一个例子: 形参复制到一个数据成员
//为了避免付出将 text复制入数据成员的过程产生的复制操作成本, 对 text实施了 std::move,从而产生了一个右值
class Annotation{
public:
explicit Annotation(const stringStr text): value(std::move(text)){
}
void printVaule();
private:
stringStr value;
};
void Annotation:: printVaule()
{
std::cout<<"value: "<<value.str<<std::endl;
}
/**
真的如题说得那样嘛?
实际上:text并非使被移动,他还是被复制入 value 得,text 已经被 std::move强制转换成为一个右值
但是,text是被声明为 const std::string得,在强制转换之前,是个左值 const std::string , 而强制转换得结果是个右值 const std::string
再看一下 string得构造函数
class stringStr{
public:
stringStr(const string& rhs){
std::cout<<"copy "<<std::endl;
}//复制构造
stringStr(string&& rhs){
std::cout<<"move "<<std::endl;
}//移动构造
std::string str = "liyushu";
/**
可以看出:因为 std::move(text)得结果是个 const std::string型别得右值
1,这个右值无法传递给 std::string得移动构造函数,因为移动构造函数只能接受非常量 std::string型别得右值引用作为形参
2,这个右值可以传递给复制构造函数,因为指涉到常量得左值引用允许绑定到一个常量右值型别得形参
*/
};
/**
以上得到两个结:
1,如果想取得对某个对象执行移动操作得能力,则不用将其声明为常量,因为针对常量执行得移动操作会变成复制操作
2,std::move 不仅不实际移动任何东西,甚至不保证经过其强制型别转换后得对象具备可移动得能力,只是其结果是个右值
*/
//std::forward :特定条件下才实施强制型别转换,分场景
//场景1:某个函数模板取用了万能引用型别为形参,随后将其传递给另一个函数
//场景1:某个函数模板取用了万能引用型别为形参,随后将其传递给另一个函数
class Widget{
};
void process(const Widget& lval){
std::cout<<"left: "<<std::endl;
}//处理左值
void process(Widget&& rval){
std::cout<<"right: "<<std::endl;
}//处理右值
template<typename T>
void logAndProcess(T&& param)
{
//把 param传递给process得函数模板
process(std::forward<T>(param));
/**
期望的是: 传入的是左值,该左值可以调用 process的左值函数,右值也一样
但是:所有函数形参都是左值,因此,logAndProcess内对process的调用都会是取用了左值型别的那个重载版本
因此:std::forward的作用就出来了:当传入右值时候,把param强制转换成右值,可以调用 process的右值版本
*/
}
//场景2:不推荐 std::forward 而推荐 std::move
//一个类,用它进行移动构造函数的调用次数的跟踪
//使用std::move实现
//使用std::move实现
class WidgetA{
public:
WidgetA(){}
WidgetA(WidgetA&& rhs): s(std::move(rhs.s)){
++moveC;
}
// private:
static std::size_t moveC;
std::string s;
};
std::size_t WidgetA::moveC = 0;
//使用std::forward实现
//使用std::forward实现
class WidgetB{
public:
WidgetB(){}
WidgetB(WidgetB&& rhs): s(std::forward<std::string>(rhs.s)){
++moveC;
}
private:
static std::size_t moveC;
std::string s;
};
std::size_t WidgetB::moveC = 0;
客户端测试
int main()
{
//测试1
std::string str = "liyy";
Annotation *ann = new Annotation(str);
ann->printVaule();
//测试2
Widget w;
//调用时传入左值
logAndProcess(w);
//调用时候传入右值
logAndProcess(std::move(w));
//测试3:
WidgetA wA;
WidgetA wAA(std::move(wA));
WidgetA wAAA(std::move(wAA));
std::cout<<wAAA.moveC<<std::endl;
WidgetB wB;
WidgetB wBB(std::move(wB));
WidgetB wBBB(std::move(wBB));
std::cout<<wAAA.moveC<<std::endl;
}
copy value:liyushu left: right: 2 2
/**
结论:
1,std::move实施的是无条件的向右值型别的强制型别转换,本身而言,不会执行移动操作
2,std::foreard仅仅对绑定到右值得引用实施向右值型别得强制转换,仅仅传递一个对象到另一个函数,保持原始对象得型别
3,运行期间,两者都不做任何操作
*/
/**
回到一个问题:遇到 T&& 时,一定是右值引用嘛?
两种含义:
1, 右值引用,仅仅会绑定到右值,识别出可移动对象
2,万能引用,可以是左值引用 T&,也可以是右值引用, 也可以绑定到 const对象或 volatile对象或非两者对象
*/
//右值引用:不涉及型别推导
//右值引用:不涉及型别推导
class Widget{
};
void f(Widget&& param);
Widget&& vara1 = Widget();
//万能引用:涉及型别推导,必要非充分条件
//1,函数模板的形参
template<typename T>
void f(T&& param);
//2, auto声明
auto&& var2 = var1;
//万能引用:首先是个引用,初始化是必需的,万能引用的初始化物会决定它代表的是个左值还是右值引用
//1,如果初始化是右值,万能引用就会对应到一个右值引用
//2,如果初始化物是左值,万能引用就会对应到一个左值引用
Widget w;
f(w);//左值被传递给f , param的型别是 Widgwt& 左值引用
f(std::move(w));//右值被传递给f, param的型别是 Widget&& 右值引用
//万能引用:扩展,以下情况不是
//1
//必须是 T&& 的形式,形如这样
//1
//必须是 T&& 的形式,形如这样
template<typename T>
void f(std::vector<T>&& param);
//f被调用时,param的型别声明的形式不是 T&& , 而是std::vector<T>&& 这就排除了 param是个万能引用的可能性,param是右值引用
//因此,如下是错误的,一个右值引用不能绑定一个左值
std::vector<int> v;
f(v);
//2
//const修饰,也不是万能引用
//2
//const修饰,也不是万能引用
template<typename T>
void f(const T&& param);
//3
//即使是 T&& 位于模板内,并不能保证一定涉及型别推导
//3
//即使是 T&& 位于模板内,并不能保证一定涉及型别推导
template<class T,class Allocator = allocator<T>>
class vector{
public:
void push_back(T&& x);
};
//形如 T&& , 但是不涉及型别推导,因为 push_back作为 vector的一部分,如不存在特定的 vector实例,则它也不存在
//该实例的具现完全决定了 push_back的声明型别,给定:
std::vector<Widget> v;
//会导致 std::vector模板具现化为如下实例
template<Widget,allocator<Widget>>
class vector{
public:
void push_back(Widget&& x);
};
//3-3: 作为3的对比,emplace_back却存在型别推导
//3-3: 作为3的对比,emplace_back却存在型别推导
template<class T,class Allocator = allocator<T>>
class vector{
public:
template <class... Args>
void emplace_back(Args&&... args);
};
//型别形参 Args独立于 vector的型别形参 T,所以 Args必须在每次 emplace_back被调用时进行推导
//名字是 Args,不是T,但仍然是个万能引用,必要非充分条件:T&& ,但不一定取名 T
//又如:
template<typename MyTemplateType>
void func(MyTemplateType&& param);
//4, auto&& 型别的变量都是万能引用
//计算任意函数调用所花费的时长
//4, auto&& 型别的变量都是万能引用
//计算任意函数调用所花费的时长
auto timeFuncInvocation = [](auto&& func, auto&&... params)
{
//开始计时
std::forward<decltype(func)>(func)(
std::forward<decltype(params)>(params)...
);
//计时器结束
};
// 如果函数模板形参具 T&& 型别,并且 的型别系推导而来,或如果
// 使用 auto&& 声明其型别,则该形参或对象就是个万能引用
// • 如果型别声明并不精确地具各 type&& 的形式,或者型别推导并未发生,则
// type&& 就代表右值引用
// • 若采用右值来初始化万能引用,就会得到一个右值引用 若采用左值来初
// 始化万能引用,就会得到一个左值引用
//1
//右值引用:std::move
//会绑定到可移动的对象上,绑定的对象可移动
//方法:把绑定到了这些对象的形参转换成右值
class Widget{
public:
Widget(){}
Widget(Widget&& rhs):name(std::move(rhs.name)), p(std::move(rhs.p)){
//rhs是右值引用
}
//private:
std::string name;
std::shared_ptr<int> p;
};
//2
//万能引用:std::forward
//只是可能绑定到可移动的对象上
class WidgetAA{
public:
WidgetAA(){}
template<typename T>
void setName(T&& newName){
name = std::forward<T>(newName);
}
//private:
std::string name;
std::shared_ptr<int> p;
};
//3
//错误写法
//以上两者不能颠倒混用:比如针对万能引用使用 std::move,会意外改动值
class WidgetBB{
public:
WidgetBB(){}
template<typename T>
void setName(T&& newName){
name = std::move<T>(newName);
}
//private:
std::string name;
std::shared_ptr<int> p;
};
std::string getWidgetName()
{
return "liyushu,liyushu";
}
//4
//初级程序员会这样改进:
//重载setName为常量左值和右值实现不同的重载
class WidgetBBB{
public:
WidgetBBB(){}
//从常量左值 取得赋值
void setName(const std::string& newName){
name = newName;
}
//从右值 取得赋值
void setName(std::string&& newName){
name = std::move(newName);
}
//private:
std::string name;
std::shared_ptr<int> p;
};
//以上改进的缺点:1,需要编写和维护更多代码 2,效率打折扣,3, 可扩展性差
/**
重点解释 2 效率的问题:
1, 如改进为 std::forward,n 被传递给 setName,然后再转手传递给 w内部的 std::string的赋值运算符
W的数据成员name可以直接从字符串字面值得到赋值,而不会产生std::string型别的临时对象
2,重载版本的 setName得到创建 std::string型别的临时对象以供其形参绑定,随后该临时对象才会移入 w的
数据成员,因此,重载版本做了如下事情:一次 std::string的构造函数来创建临时对象,一次std::string的
移动赋值运算符来移动 newName到 w.name,还有一次std::string的析构函数来销毁临时变量
解释3:扩展问题
一个形参可以重载,两个,多个形参呢?你给我重载看看?比如下面的实现
*/
//5
//常用的多参数重载版本
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);
//6
//某些情况,想要再单一函数内将谋个对象不止一次地绑定到右值引用或万能引用,而且想保证完成对该对象地其他所有操作之前
//其值不被移走,在这种情况下,你就得仅在最后一次使用该引用时候,对其实施 std::move或 std::forward
// template<typename T>
// void setSignText(T&& text)
// {
// //text是个万能引用
// //使用text但不改动其值
// sign.setText(text);
// //text地值不会被 setText修改,需要调用add时候使用该值,仅仅在万能引用最后一次使用
// //有条件地将 text强制转换成右值型别
// signHistory.add(std::forward<T>(text));
// }
//7
//在按值返回地函数中,如果返回地是绑定到一个右值引用或一个万能引用地对象,
//则当你返回该引用时,应该对其实施 std::move或者std::forward
// class Matrix{
// public:
// //重载+操作
// friend Matrix operator+=(Matrix&& lhs, const Matrix& rhs);
// };
// //以下编译有 bug
// Matrix operator+=(Matrix&& lhs, const Matrix& rhs)
// {
// //常量左值引用既可以操作左值,也可以操作右值 rhs
// //右值引用必须立即进行初始化操作,且只能使用右值进行初始化
// lhs += rhs;
// return std::move(lhs);//lhs移入返回值
// // return lhs;//lhs复制入返回值,没有移动快
// }
//8
//std::forward也类似,如果原始对象是一个右值,它的值应当被移动到返回值上,这样可以避免制造副本的成本
//但如果原始对象是一个左值,那就必须创建出实实在在的副本
class Fraction{
};
class Reduce{
public:
void reduce(){
std::cout<<"I am liyushu! "<<std::endl;
}
};
template<typename T>
T reduceAndCopy(T&& frac)
{
frac.reduce();
return std::forward<T>(frac);
//对于右值 是移入返回值
//对于左值 是复制入返回值
//如果省去 std::forward的调用,则 frac会无条件地复制到 函数 返回值中
}
//https://www.cnblogs.com/wangpei0522/p/4472548.html
//9
//局部变量地返回:不能用 std::move或std::forward (复制转为移动,没门!!!!!!)
Widget makeWidget()
{
Widget w;
w.name = "love liyushu";
//return std::move(w);//错误!
return w;//正确?
//这里返回地不是局部对象w, 而是w的引用,std::move(w)的结果
}
/**
编译器如要在一个按值返回地函数里省略对局部对象地复制或移动,需要满足两个条件:
1,局部对象型别和函数返回值型别相同
2,返回地就是局部对象本身
因此上述代码可以改为:return w;
*/
//https://blog.csdn.net/Jacky_Feng/article/details/120742414
客户端测试:
int main()
{
//测试1:
Widget wa;
wa.p = std::shared_ptr<int>(new int(1));
wa.name = "lys";
Widget wb(std::move(wa));
std::cout<<wb.name<<" "<<wb.p<<std::endl;
//测试2:
WidgetAA waa;
std::string&& str = "liyushu";//右值
std::string str1 = "liyushu";//左值 都OK
waa.setName(str);
std::cout<<" "<< waa.name <<std::endl;
//测试3:
WidgetBB wbb;
auto n = getWidgetName();
std::cout<<"wbb: "<< wbb.name <<" n:"<< n <<std::endl;//1
wbb.setName(n);
std::cout<<"wbb: "<< wbb.name <<" n:"<< n <<std::endl;//2
/**
说明:1 n输出正常,2 n输出异常
因为: n是个局部变量,setName函数内部使用了 std::move把它的引用形参无条件地强制转换到右值
n的值被移动到 w.name,这样一来,调用完 setName函数返回时,n将变成一个不确定的值
改进:
std::move改为 std::forward
*/
//测试4:
WidgetBBB wbbb;
auto nb = getWidgetName();
std::cout<<"wbbb: "<< wbbb.name <<" nb: "<< nb <<std::endl;//1
wbbb.setName(nb);
std::cout<<"wbbb: "<< wbbb.name <<" nb: "<< nb <<std::endl;//
//测试7:
// Matrix a,b;
// Matrix c = std::move(a) + b;
//测试8:
Reduce re;
Reduce fa = reduceAndCopy(std::move(re));
//测试9”
Widget www = makeWidget();
std::cout<<"WWW: "<<www.name<<std::endl;
}
lys 0x55acd2355eb0 liyushu wbb: n:liyushu,liyushu wbb: liyushu,liyushu n: wbbb: nb: liyushu,liyushu wbbb: liyushu,liyushu nb: liyushu,liyushu I am liyushu! WWW: love liyushu
// 1, 针对右值引用的最后一次使用实施 std: :move, 针对万能引用的最后一次使
// 用实施 std:: forward
// 2, 作为按值返回的函数的右值引用和万能引用,依上一条所述采取相同行为
// 3, 若局部对象可能适用于返回值优化 则请勿针对其实施 std: :move 或 std::forward
//假设这样一种情况:
//取用一个名字作为形参,记录当下时间和日期,再把该名字添加到一个全局数据结构
//打印函数
template<class T>
void Print(T first, T last)
{
for(; first != last; ++first)
std::cout<< *first <<" ";
std::cout<<std::endl;
}
template<class T>
void log(T time, const std::string& name)
{
}
//实现1:
//实现1:
std::multiset<std::string> names;
void logAndAdd(const std::string& name)
{
auto now = std::chrono::system_clock::now();
log(now,"logAndAdd");
names.emplace(name);
}
//实现1无可厚非,但是考虑效率却不行
//实现2:改进1中复制带来的效率问题
//实现2: 改进1中复制带来的效率问题
template<typename T>
void logAndAdd(T&& name)
{
auto now = std::chrono::system_clock::now();
uint64_t dis_millseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()
- std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count() * 1000;
time_t tt = std::chrono::system_clock::to_time_t(now);
auto time_tm = localtime(&tt);
char strTime[25] = { 0 };
sprintf(strTime, "%d-%02d-%02d %02d:%02d:%02d %03d", time_tm->tm_year + 1900,
time_tm->tm_mon + 1, time_tm->tm_mday, time_tm->tm_hour,
time_tm->tm_min, time_tm->tm_sec, (int)dis_millseconds);
std::cout << strTime << std::endl;
log(now,"logAndAdd");
names.emplace(std::forward<T>(name));
}
//实现3:
//重载版本 int
//返回索引对应的名字
//实现3:
//重载版本 int
//返回索引对应的名字
std::string nameFromIdx(int idx);
void logAndAdd(int idx)
{
auto now = std::chrono::system_clock::now();
std::cout<<"int: "<<std::endl;
log(now,"logAndAdd");
//names.emplace(nameFromIdx(idx));
}
//结论
//形参为万能引用的函数,在具现过程中,和几乎任何实参型别都会产生精确匹配,一旦万能引用成为重载候选
//它就会吸引大批的实参型别
//实现4:
//如何解决:撰写一个带完美转发的构造函数
//实现4:
//如何解决:撰写一个带完美转发的构造函数
class Person{
public:
//1,完美转发构造函数初始化了数据成员
template<typename T>
explicit Person(T&& n): name(std::forward<T>(n)){
std::cout<<"forward: "<<std::endl;
}
//2, 形参为int的构造函数
explicit Person(int idx): name(nameFromIdx(idx)){
std::cout<<"int: "<<std::endl;
}
//3, 编译器生成 复制构造函数
Person(const Person& rhs){
std::cout<<"copy: "<<std::endl;
}
//4, 编译器生成 移动构造函数
Person(Person&& rhs){
std::cout<<"move: "<<std::endl;
}
private:
std::string name;
};
//因此,不要用万能引用作为重载版本
客户端测试:
int main()
{
//测试1:
logAndAdd("liyushu");
Print(names.begin(),names.end());
std::string petName("lishushu");
//传递左值std::string
//name是个左值,它是被复制入 names的,没人任何办法避免这个复制操作,因为传递给 logAndAdd的是个左值 petName
logAndAdd(petName);
//传递右值 std::string
//形参ame 绑定到了一个右值,name自身是个左值,所以它是被复制入 names的,这个调用中,付出一次复制的成本,可以用
//一次移动达到同样目标
logAndAdd(std::string("liyushushu"));
//传递字符串
//形参name还是绑定到一个右值,但这次这个 std::string型别的临时对象是从 “liyangyyyy”隐式构造的,name是被复制入names的
logAndAdd("liyangyyyy");
//测试2:
//将左值复制入 multiset
logAndAdd(petName);
//对右值实施移动而非复制
logAndAdd(std::string("love liyushu"));
//在multiset中直接构造一个 std::string对象,而非复制一个 std::string型别的临时对象
logAndAdd("shushushu");
//测试3:
//调用重载版本
logAndAdd(22);
//如下发生错误
short nameIdx;
//logAndAdd(nameIdx);//错误!
/**
logAndAdd有两个重载版本
1, 形参型别为 T&& 的版本可以将T推导为short,从而产生一个精确匹配
2,形参型别为 int的版本只能在型别提升以后才能匹配到 short型别的实参
精确匹配优先于提升以后才能匹配,所以,形参型别为万能引用的版本才是被调用的版本
为什么short传给万能引用却报错呢?
1,调用执行后,形参name被绑定到传入的 short型别的变量上
2, name被std::forward传递给 names的emplace成员函数
3, name又被转发给 std::string的构造函数,但是 std::string的构造函数中并没有形参为 short的版本
*/
//测试4:
Person p("nacy");
//Person pp(p);
/**
调用的是 forward版本
非常量左值 p 被初始化,模板构造函数可以实例化来接受 Person型别的非常量左值形参
*/
const Person cp("nacy");
Person cpp(cp);
}
//方法1: 传值
//提升性能的方法:把传递的形参引用型别替换成值型别
//当你知道肯定需要赋值形参时,考虑按值传递对象
std::string nameFromIdx[4] = {"liyushu","lishushu","lxiaoshu","liyuyu"};
class Person{
public:
//替换掉T&& 型别的构造函数
explicit Person(std::string n):name(std::move(n)){
std::cout<<"string: "<<std::endl;
}
explicit Person(int idx):name(nameFromIdx[idx]){
std::cout<<"int: "<<std::endl;
}
private:
std::string name;
};
//方法2:标签分配
//不想舍弃重载,又不想舍弃万能引用,该怎么办呢?
//如果万能引用仅是形参列表的一部分,该列表中还有其他非万能引用型别的形参的话,那么只要该非万能引用形参
//具备充分差的匹配能力,则它就足以将这个带有万能引用形参的重载版本踢出局
//改造 e26 中的函数
template<class T>
void Print(T first, T last)
{
for(; first != last; ++first)
std::cout<< *first <<" ";
std::cout<<std::endl;
}
template<class T>
void log(T time, const std::string& name)
{
}
std::multiset<std::string> names;
template<typename T>
void logAndAdd(T&& name)
{
auto now = std::chrono::system_clock::now();
log(now,"logAndAdd");
names.emplace(std::forward<T>(name));
}
//如 e2 一样,若出现重载 int 的版本,会出现调用问题
//改进2:
//重新实现 logAndAdd,把它委托两个函数,一个接受整型值,另一个接受其他所有型别
//而 logAndAdd 本身则接受所有型别的实参,无论整型还是非整型
template<typename T>
void logAndAddImpl(T&& name,std::false_type)//非整型实参,才进行下面步骤
{
auto now = std::chrono::system_clock::now();
log(now,"logAndAdd");
std::cout<<"logAndAddImpl: false"<<std::endl;
//names.emplace(std::forward<T>(name));
}
template<typename T>
void logAndAddImpl(T&& name,std::true_type)//非整型实参,才进行下面步骤
{
auto now = std::chrono::system_clock::now();
log(now,"logAndAdd");
std::cout<<"logAndAddImpl: true"<<std::endl;
//names.emplace(std::forward<T>(name));
}
//改进2-1
template<typename T>
void logAndAdd21(T&& a)
{
cout<<boolalpha;
cout<<is_integral<T>()<<endl;
logAndAddImpl(std::forward<T>(a),is_integral<T>());
//第二个参数用来检测传入的是否是整型
//但是,如果传给万能引用name的实参是个左值,那么 T 就被被推导为左值引用,所以,如果传递
//给 logAndAdd的是个左值 int,则 T 就会被推导 为 int&。这不是个整型,因为引用型别都不是整型
//所以,std::is_intergral<T> 在函数接受了任意左值实参时候,会得到结果 假
//改进2-2:
//std::remove_reference 正如其名,也正如所需,它会移除型别所附的一切引用修饰词
logAndAddImpl(std::forward<T>(a),std::is_integral<typename std::remove_reference<T>::type>());// C++11
//logAndAddImpl(std::forward<T>(a),std::is_integral<typename std::remove_reference_t<T>);//C++14
}
客户端测试
int main()
{
//测试1:
Person pp(12.0);
//测试2-1:
int a=1;
int b =10;
logAndAdd21(a);//传递左值 false
logAndAdd21(10);//传递右值 true
}
int: false logAndAddImpl: false logAndAddImpl: true true logAndAddImpl: true logAndAddImpl: true