做搜狗pc网站优化点,下载wordpress低版本,中国空间站模型,陕西西安网站建设公司排名文章目录 1. 引用折叠2. 万能引用3. 完美转发3.1对比#xff1a;std::move and std::forward比较 3.2使用时机3.3 返回值优化#xff08;RVO)两个前提条件注意事项 4. 完美转发失败情况完美转发失败五种情况 完美转发的实现要依赖于模版类型推导和引用折叠和万能引用。
1. 引… 文章目录 1. 引用折叠2. 万能引用3. 完美转发3.1对比std::move and std::forward比较 3.2使用时机3.3 返回值优化RVO)两个前提条件注意事项 4. 完美转发失败情况完美转发失败五种情况 完美转发的实现要依赖于模版类型推导和引用折叠和万能引用。
1. 引用折叠
在C中“引用的引用是非法的。像auto rx x;(两个引用之间有空格),这种直接定义引用的引用是不合法的但是在编译器通过类型别名或模版类型推导等语境中可能间接定义出引用 的引用”,这是引用会形成折叠引用折叠会发生的场景模版的实例化auto类型推导创建和运用typedef 和别名声明以及decltype语境
引用折叠规则
所有的引用折叠最终都代表一个引用要么是左值引用要么是右值引用。规则就是
如果任一引用为左值引用则结果为左值引用。否则即两个都是右值引用结果为右值引用。
-----根本原因是在一处的声明为左值就说明该对象为持久对象编译器就必须此对象可靠(左值《Effective Modern C》
x 折叠为 x
x x , x 都折叠为 x编程实验
class widget
{};templatetypename T
void func(T param)
{}widget widgetFactory()
{return widget();
}templatetypename T
class Foo
{
public:
typedef T RvalueRefToT;
};int main()
{int x 0;int rx x;//折叠引用发生的场景1.模版模版实例化widget w1;func(w1); //w1为左值T被推导为widget , 代入得void func(Widget param);//引用折叠后得void func(Widget param)func(widgetFactory()); //传入右值T被推导为widget,代入的得void func(Widget param)//这里没有发生引用折叠//折叠引用发生的场景2.---auto类型推导auto w2 w1; //w1为左值auto被推导为widget,代入得widget , 引用折叠后widget w2;auto w3 widgetFactory(); //函数返回widget,为右值auto被推导为widget代入的 widget w3;//引用折叠发生的语境3——tyedef和usingFooint f; //T被推导为int ,代入得typedef int RvalueRefToT,引用折叠后typedef int RvalueRefToT//引用折叠发生的语境4——decltypedecltype(x) var1 10; //由于x是int类型 代入得 int var1;decltype(rx) var2 x; //由于rx为int 类型代入得 int var2;return 0;
}2. 万能引用
万能引用既可以接受左值类型的参数又可以接受右值类型的参数。
T含义 当T是一个具体的类型时T表示右值引用只能绑定到右值。 当涉及T类型推导时T为万能引用。若用右值初始化万能引用则T为右值引用。若用左值初始化万能引用则T为左值引
条件T是万能引用的两个条件
1必须涉及类型推导
2声明的形式也必须正好形如“T”。并且该形式被限定死了任何对其修饰都将剥夺T成为万能引用的资格
templatetypename T
ReturnType Function(T parem)
{// 函数功能实现
}编程实验
#include iostream
using namespace std;void fun(int x) { cout call lvalue ref endl; }
void fun(int x) { cout call rvalue ref endl; }
void fun(const int x) { cout call const lvalue ref endl; }
void fun(const int x) { cout call const rvalue ref endl; }templatetypename T
void PerfectForward(T t)
{std::cout T is a ref type?: std::is_referenceT::value std::endl;std::cout T is a lvalue ref type?: std::is_lvalue_referenceT::value std::endl;std::cout T is a rvalue ref type?: std::is_rvalue_referenceT::value std::endl;fun(forwardT(t));
}int main()
{PerfectForward(10); // call rvalue refint a 5;PerfectForward(a); // call lvalue refPerfectForward(move(a)); // call rvalue refconst int b 8;PerfectForward(b); // call const lvalue refPerfectForward(move(b)); // call const rvalue refsystem(pause);return 0;
}/*输出结果
T is a ref type?: 0
T is a lvalue ref type?: 0
T is a rvalue ref type?: 0 //万能引用绑定右值T会被推导为 T 类型
call rvalue refT is a ref type?: 1
T is a lvalue ref type?: 1
T is a rvalue ref type?: 0 //万能引用绑定左值T会被推导为T 类型
call lvalue refT is a ref type?: 0
T is a lvalue ref type?: 0
T is a rvalue ref type?: 0
call rvalue refT is a ref type?: 1
T is a lvalue ref type?: 1
T is a rvalue ref type?: 0
call const lvalue refT is a ref type?: 0
T is a lvalue ref type?: 0
T is a rvalue ref type?: 0
call const rvalue ref
*/利用引用折叠进行万能引用初始化类型推导
当万能引用T param 绑定到左值时由于万能引用也是引用而左值只能绑定到左值引用因此T会被推导为T 类型从而 param 的类型为 T 引用折叠后类型变为 T当万能引用T param 绑定到左值时右值只能绑定到右值引用因此T会被推导为 T 类型从而 param 的类型为 T(右值引用)。
总结万能引用就是利用模板推导和引用折叠的相关规则生成不同的实例化模板来接收传进来的参数。
3. 完美转发
指的是 函数模板可以将自己的参数 “完美” 地转发给内部调用的其它函数 不仅能准确地转发参数的值还能 保证被转发参数的左、右值属性不变 。
//左值版本
templateclass T
constexpr T forward(std::remove_reference_tT arg) noexcept
{// forward an lvalue as either an lvalue or an rvaluereturn (static_castT(arg));//可能发生引用折叠
}
//右值版本
templateclass T
constexpr T forward(std::remove_reference_tT arg) noexcept
{// forward an rvalue as an rvaluereturn (static_castT(arg));
}为什么引入完美转发
编程实验:
void print(const int t) //左值版本
{cout void print(const int t) endl;
}
void print(int t) //右值版本
{cout void print(int t) endl;
}templatetypename T
void testForward(T parm)
{//不完美转发print(parm); //parm 是形参是左值。调用void print(const int t),永远调用左值版本 print(std::move(parm)); //parm是 右值。调用void print(int t),永远调用右值版本//完美转发print(std::forwardT(parm));//只有这里才会根据传入parm的实参类型的左右值进行转发
}
int main()
{cout 测试右值: endl;testForward(1);cout 测试左值: endl;int x 1;testForward(x);return 0;
}/*输出结果
测试右值
void print(const int t)
void print(int t)
void print(int t) //完美转发这里转入的值为1为右值调用右值版本的print
测试左值
void print(const int t)
void print(int t)
void print(const int t)//完美转发这里转入的值为x为左值调用左值版本的print
*/分析std:forward 实现条件转发的原理以widget类对象为例 :::info 总结
当传递给func函数的实参类型是左值widget时T被推导为widget类型然后forward会实例化为std:forwardwidget,并返回widget(左值引用。当传递给func函数的实参类型是右值widget时T被推导为widget类型然后forward会实例化为stdforward,并返回widget(右值引用注意匿名的右值引用是右值。std:forward会根据传递给func函数的实参的左/右值类型进行转发当传递给func函数左值实参时forward返回左值引用并将改值转发给process函数而当传递给func函数右值实参时forward返回右值引用并将右值转发给process函数。
:::
3.1对比std::move and std::forward
比较
move和forward都是仅仅执行强制类型转换的函数std:move 无条件的将实参强制转换为右值。而std:forward 则仅是在某个特定的条件满足时转入func函数的实参时右值才进行强制类型转换。std:move 并不进行任何移动std:forward 也不进行任何转发。这两者都在运行期间无所作为他们都不会再生成任何可执行代码。连一个字节都不会生成。
3.2使用时机
针对右值引用的最后一次使用实施std:move,针对万能引用的最后一次使用实施std:forward。在按值返回的函数中如果返回的是一个绑定在右值引用或万能引用的对象时可以实施std:move 或 std:forward。因为如果原始对象是右值它的值就应被移动到返回值上而如果是左值就必须通过复制构造出副本作为返回值。
3.3 返回值优化RVO)
两个前提条件
局部对象类型和函数返回值类型相同。返回的就是局部对象本身。包含局部对象和作为return语句中的临时对象等。
注意事项
在RVO的前提条件满足时要么避免复制要么会自动调用std::move隐式实施于返回值。按值传递的函数形把他们作为函数的返回值时情况与返回值优化类似编译器会自动选择第二种处理方案即返回时将形参转为右值处理。如果局部变量有资格进行RVO优化就不要将std:move 或 std:forward用在这些变量上因为这可能会让返回值丧失优化的机会。 //针对右值引用实施std:move 针对万能引用实施stdforward
class Data {};
class widget
{std::string _name;std::shared_ptrData _ptr;
public:widget(){cout widget() endl;}//复制构造函数widget(const widget w):_name(w._name), _ptr(w._ptr){cout widget(const widget w) endl;}//针对右值引用使用std:movewidget(widget rhs):_name(std::move(rhs._name)), _ptr(std::move(rhs._ptr)){cout widget(widget w) endl;}//针对万能引用使用std:forward.//注意这里使用万能引用来代替两个重载版本void setName(const string )和 void setName(string)//好处是当使用字符串字面量时万能引用的版本的效率更高如w.setName(santa),此时字符串会被推导为//const char ()类型然后直接转给setName函数(可以避免先通过字面量来构造string对象) //并将该类型直接转给name的构造函数节省了一个构造和释放临时对象的开销效率更高templateclass Tvoid setNaem(T newname){if (newname ! _name)//第一次使用newname{_name std::forwardT(newname);//针对万能引用的最后一次实施forward}}
};//2. 按值返回函数//2.1 按值返回的是一个绑定到右值引用的对象class complex {double _x;double _y;public:complex(double x 0, double y 0):_x(x),_y(y){}complex operator(const complex rhs){_x rhs._x;_y rhs._y;return *this;}};complex operator(complex lhs, complex rhs){lhs rhs;return std::move(lhs);}//2.2 按值返回绑定到一个万能引用对象templatetypename T auto test(T t){return std::forwardT(t);//由于t是一个万能引用对象。按值返回时实施std:forward//如果对象是一个右值则被移动到返回值上如果对象是左值//则会被拷贝到返回值。}//3. RVO优化//3.1 返回局部对对象widget makewidget(){widget w;return w; //返回局部对象瞒足RVO优化的两个条件。为避免复制会直接在返回值内存上创建w对象。//如果改为std:move(w);由于返回值类型不同(widget 返回值类型是 widget)//会剥夺RVO优化的机会就会先创建w对象在移动给返回值无形中增加了移动动作。//对于这种满足RVO优化条件的在某些条件下无法避免复制(如多路返回),编译器仍会//将w转为右值即 return std:move(w) ,而无需显示的std:move!!}//3.2按值形参作为返回值widget makewidget(widget w)//注意这里的w是按值传递的{//...return w;//这里虽然不满足RVO条件(w是形参不是函数的局部对象)当时仍会被编译器优化默认的转为右值}
int main()
{cout 1. 针对右值引用实施std::move针对万能引用实施std::forward endl;widget w;w.setNaem(SantaClaus);cout 2. 按值返回时 endl;auto t1 test(w);auto t2 test(std::move(w));cout 3. RVO优化 endl;widget w1 makewidget(); //按值返回局部对象widget w2 makewidget(w1); //按值返回形参对象return 0;
}
//运行结果
/*
1. 针对右值引用实施std::move针对万能引用实施std::forward
widget()
2. 按值返回时
widget(const widget w)
widget(widget w)
3. RVO优化
widget()
widget(widget w)widget(const widget w)
widget(widget w)
*/4. 完美转发失败情况
完美转发失败
完美转发不仅转发对象还转发其类型左右值特征以及是否带有const 或 volation 等修饰词。而完美转发的失败主要来源于模版类型推导失败或结果是错误的类型。实例说明假设转发的目标函数f而转发函数fwd天然就应该是泛型
templatetypename... Ts
void fwd(Ts... param)
{f(std::forwardTs(param)...);
}f(experssion); //如果本语句执行了某操作
fwd(experssion);//而用同一实参调用fwd 则会执行不同操作则完美转发失败五种情况
使用{}初始化列表时
分析由于转发函数是模版函数而在模版类型推导中大括号不能自动推导为std:initializer_list
解决方案用auto 声明一个局部变量在将局部变量传递给转发函数。
0和NULL作空指针时
分析:0或NULL以空指针之名传递给模版时类型推导的结果是整型而不是所希望的指针类型。
解决方案传递nullptr而非0或NULL。
仅声明static成员变量而无定义时
分析C中常量一般是进入符号表中的只有对其取地址是才会实际分配地址调用f函数时其实参是直接从符号表中取值此时不会出现问题但当调用fwd由于其形参是万能引用而引用本质上是可解引用的指针。因此当传入fwd会要求准备某块内存以供解引用出该变量出来但因其未定义也就没有实际的内存空间编译时可能失败。注意如果是static const 声明的常量的值是整形或者布尔型的那么就可以直接在类中进行定义初始化如果是浮点数和自定义类型那么就需要再类外进行定义初始化
解决方案在类外定义该成员变量。注意着这变量在生声明时一般会先给初始值因此在定义时无需也不能再重复指定初始值
使用重载函数名或模版函数名时
分析由于fwd是模版类型参数其形参没有任何类型的信息。当传入重载函数名或模版函数时代表许多函数就会导致fwd的形参不知绑定到哪一个函数上。
解决方案在调用fwd函数时手动为形参指定类型信息。
转发位域时
分析位域是由机器字的若干任意部分组成(任意int的3 到 5个比特位)但这样的实体是无法取地址的而fwd的形参是引用本质就是指针所以也没有办法创建指向任意比特的指针。
解决方法制作位域的副本并以该副本来调用转发函数。
//大括号初始化列表
void f(const std::vectorint v)
{cout void f(const std::vectorint v) endl;
}
//2. 用0或NULL作空指针时
void f(int x)
{cout void f(intx) endl;
}
//3.仅声明static const的整型变量而无定义
class widget
{
public:static const double MinValue ;//仅声明无定义静态变量需要再类外定义
};
const double widget::MinValue 28.10;//在类外定义无需也不能重复定义初始值
//4. 使用重载函数名或模版函数名
int f(int (*pf)(int))
{cout int f(int (*pf)(int)) endl;return 0;
}
int processVal(int value)
{return 0;
}
int processVal(int value,int priority)
{return 0;
}//5. 位域
struct IPv4Header
{
std::uint32_t version : 4,
IHL : 4,DSCP : 6,ECN : 2,totalLength : 16;
//...
};
templatetypename T
T workonval(T param)//模版函数代表许多函数
{return param;
}//用于测试的转发函数
templatetypename ...Ts
void fwd(Ts ...param)
{f(std::forwardTs(param)...);//目标函数
}
int main()
{cout -------------------1. 大括号初始化列表--------------------- endl;//1.1 用同一实参分别调用f和fwd函数f({ 1,2,3 });//{1,2,3}会被隐式转函为vectorint//fwd({ 1,2,3 });//编译失败由于fwd是模版函数而模版推导时{}不能被自动推导为std::initializar_listint//解决方案auto il { 1,2,3 };fwd(il);cout -------------------2. 0或NULL用作空指针------------------- endl;//2.1 用同一实参分别调用f和fwd函数f(NULL); //调用void f(int) 函数fwd(NULL); //NULL被推导为int仍调用void f(int )//2.2 解决方案:使用nullptrf(nullptr); //匹配int f(int (*pf)(int ))fwd(nullptr);cout -------3. 仅声明static const的整型成员变量而无定义-------- endl;//3.1 使用同一实参分别调用f和fwd函数f(widget::MinValue); //调用用void f(int) 函数实参从符号表中取得编译成功fwd(widget::MinValue); //fwd的形参是引用而引用的本质是指针但fwd使用到该实参时需要解引用//这里会因为没有为MinValue分配内存·而出现编译错误//3.2解决方案在类外定义该变量cout -------------4. 使用重载函数名或模板函数名--------------- endl;//4.1 使用同一实参分别调用f和fwd函数f(processVal); //由于形参是int(*pf)(int),带有类型信息会匹配int processVal(int value)//fwd(processVal);//error,fwd的形参不带任何类型信息不知到会匹配到哪个processVal函数//4.2 解决方案using processFuncType int(*)(int);processFuncType processValOPtr processVal;fwd(processValOPtr);fwd(static_castprocessFuncType(workonval));//调用int f((*pf)(int))cout ----------------------5. 转发位域时--------------------- endl;//5.1 用同一实参分别调用f和fwd函数IPv4Header ip {};f(ip.totalLength); //调用void f(int )//fwd(ip.totalLength); //errorfwd形参是引用由于位域是比特位组成的无法比特位的引用//解决方案创建位域的副本并传给fwdauto length static_caststd::uint16_t(ip.totalLength);fwd(length);return 0;}
/*输出结果
-------------------1. 大括号初始化列表---------------------
void f(const std::vectorint v)
void f(const std::vectorint v)
-------------------2. 0或NULL用作空指针-------------------
void f(intx)
void f(intx)
int f(int (*pf)(int))
int f(int (*pf)(int))
-------3. 仅声明static const的整型成员变量而无定义--------
void f(intx)
void f(intx)
-------------4. 使用重载函数名或模板函数名---------------
int f(int (*pf)(int))
int f(int (*pf)(int))
int f(int (*pf)(int))
----------------------5. 转发位域时---------------------
void f(intx)
void f(intx)
*/参考博客链接: link 链接: link 链接: link 全文完