设计创意网站推荐,金本网站建设设计,做网站需要写代码,上海建网站工作室大家好#xff0c;我叫徐锦桐#xff0c;个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识#xff0c;还有日常折腾的经验#xff0c;欢迎大家来访。 在写STL源码的时候遇到的问题#xff0c;在这里写一篇笔记记录一下。 一、引用折叠
引用折叠… 大家好我叫徐锦桐个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识还有日常折腾的经验欢迎大家来访。 在写STL源码的时候遇到的问题在这里写一篇笔记记录一下。 一、引用折叠
引用折叠表示了一个值被引用多次时只有在模板推导时候会生成什么类型。 T 折叠成 T T 折叠成 T T 折叠成 T T 折叠成 T 其实总结起来就是只有两次都是右值引用的时候才是T即T 折叠成 T其他都是转换成左值引用T。
二、左值引用
左值引用就是个别名将一个变量绑定到另一个变量上。
int a 10;
int b a; // 左值引用
const int c 10; // 常量左值引用b 20;
std::cout a std::endl; // 20修改引用变量会影响原来的变量的值但是 const int 不能修改原对象的值中间的int可以更换为其他类型。 一般来说函数传参的时候是拷贝传参也就是将变量复制到一个临时变量再将临时变量传入函数最后销毁临时变量。这里涉及到了内存的拷贝消耗的时间比较长。但是如果在传参的时候使用左值引用不会涉及到内存的拷贝因为是别名相当于直接修改原变量了。
void left_value(int x) {x 200;
}int main()
{ int value 10;left_value(value);std::cout value std::endl; // 200return 0;
}当然如果是const的话虽然不涉及到内存的拷贝过程但是不能修改原变量。
void left_value(const int x) {x 200; // error: assignment of read-only reference x
}int main()
{ int value 10;left_value(value);std::cout value std::endl; // 200return 0;
}这里函数传参的时候为什么 int 能匹配到 int 或者 const int 呢 当你将一个int类型传递给int参数时c会执行一个类型转换将int隐式的转换为int另一个同理。注意int、int、const int 在匹配函数的时候优先级一样同时出现会发生歧义错误。 函数传参的时候隐式转换很常见。
void left_value(int num) {std::cout num std::endl;
}int main()
{ float a 1.5;left_value(a); // 1return 0;
}传入的时候隐式的将float类型转化为int类型。 但是用函数重载就可以解决这个问题。
void left_value(int num) {std::cout num std::endl;
}void left_value(float num) {std::cout num std::endl;
}int main()
{ float a 1.5;left_value(a);return 0;
}会调用最适合的函数那如果没有的话就只能进行隐式的类型转换了。
三、右值引用
右值引用是c11引用的新特性。就是左值引用是给一个变量加别名而右值引用就是绑定变量到一个临时值上临时变量的生命周期和新左值的生命周期绑定原来的临时变量销毁。
int d 10; // 右值引d 200; // 此时 d 是变量是左值引用在c中是一个特别的类型因为它的值类型和变量类型不一样, 左值/右值引用变量的值类型都是左值, 而不是左值引用或者右值引用。 这句话意思非常重要简单来说看上面代码一开始将d绑定到了10上面然后又将d赋值在int d10 之后每次使用dd都是作为一个左值使用的。一个右值引用变量在使用上就变成了左值已经不再携带其是右引用这样的信息只是一个左值。 右值引用也是提高性能用的。
vectorstring v;
string s teststring;
v.push_back(s);上面这个代码用了一个临时变量。假如我们啥都不干那么是怎么进行的呢首先会先创建一个临时字符串然后将这个临时字符串拷贝到v的内存上然后再销毁这个临时字符串。中间进行内存的开辟和销毁在一般数据上性能没啥问题如果是数据特别多的话性能就会严重下降。 但如果我们用右值引用如下代码。
vectorstring v;
string s teststring;
v.push_back(std::move(s));这里的std::move()是将一个左值强制转换为右值下面我会讲现在就知道能将左值强制转化为右值就行了。 中间的过程就变成了创建一个临时字符串然后直接将这个临时字符串挂载到v上面。是不是中间少了好多过程性能也大大的提高了。
四、万能引用
模板编程中有的时候需要传入左值也传入右值引用。 下面这个只能传入一个左值如果传入右值就会报错。
template typename T
void func(T value) {std::cout 函数调用 std::endl;
}int main()
{int value 10;func(value);func(10); // 报错return 0;
}这时候就用到了我们的万能引用T。
template typename T
void func(T value) {std::cout 函数调用 std::endl;
}int main()
{int value 10;func(value);func(10); return 0;
}解释一下: 能同时传入左值引用和右值引用。 如果是左值T先会推导成T然后再发生引用折叠如果是右值引用T会推导成T。 比如说func(value)中的value是一个左值传入后T会推导成T然后再后后面的发生折叠引用就会变成T。 func(10)中的10是一个右值T会推导成T然后再和后边的发生引用折叠最后变成了T。 **T只在
五、move
std::move它其实啥都没干就是强制将左值转化为右值。 show you the code。
templatetypename _Tpconstexpr typename std::remove_reference_Tp::typemove(_Tp __t) noexcept{ return static_casttypename std::remove_reference_Tp::type(__t); }这里面的 std::remove_reference_Tp就像去除变量的引用假如_Tp是int最后返回type是个int。 std::remove_reference_Tp的源代码如下。
/// remove_referencetemplatetypename _Tpstruct remove_reference{ typedef _Tp type; };templatetypename _Tpstruct remove_reference_Tp{ typedef _Tp type; };templatetypename _Tpstruct remove_reference_Tp{ typedef _Tp type; };templatetypename _Tp, bool __is_referenceable_Tp::valuestruct __add_lvalue_reference_helper{ typedef _Tp type; };templatetypename _Tpstruct __add_lvalue_reference_helper_Tp, true{ typedef _Tp type; };六、完美转发
完美转发是配合万能引用使用的通过万能引用传入左值和右值的参数然后通过完美转发保留这个属性转发给对应的重载函数。 show the code。
/*函数模板的重载
*/
template typename T
void to_forward(T value) {std::cout 调用的左值函数 std::endl;
}template typename T
void to_forward(T value) {std::cout 调用的右值函数 std::endl;
}template typename T
void func(T arg) {to_forward(arg); // 调用左边值函数
}int main(){int value 10;func(std::move(value));
}在这个代码中func函数中的to_forward函数最终会调用to_forward(T value)虽然传入的是右值一个右值引用变量在使用上就变成了左值已经不再携带其是右引用这样的信息只是一个左值所以这里调用的是左值的重载函数。 首先展示一下std::forward的源码。
/*** brief Forward an lvalue.* return The parameter cast to the specified type.** This function is used to implement perfect forwarding.*/templatetypename _Tpconstexpr _Tpforward(typename std::remove_reference_Tp::type __t) noexcept{ return static_cast_Tp(__t); }/*** brief Forward an rvalue.* return The parameter cast to the specified type.** This function is used to implement perfect forwarding.*/templatetypename _Tpconstexpr _Tpforward(typename std::remove_reference_Tp::type __t) noexcept{static_assert(!std::is_lvalue_reference_Tp::value, template argument substituting _Tp is an lvalue reference type);return static_cast_Tp(__t);}这里有两个重载的函数一个对应左值一个对应右值。 forward(typename std::remove_reference_Tp::type __t)这个是先将_t的类型去掉引用然后加上个也就是最后是左值类型。 那下面forward(typename std::remove_reference_Tp::type __t)那个肯定就是右值的了。 这里也用到了引用折叠连个返回的方法是一样的都是return static_cast_Tp(__t)如果_Tp是然后在和后面的折叠就是左值了如果_Tp是然后再和后面的折叠还是。 std::move和std::forward其实什么都没做只是强制转换了一下类型。 看下面这个代码这个是正确的。我们通过完美转发保留了这个左值还是右值的属性然后再传给另一个函数。
#include iostream
#include utility/* 完美转发 万能引用 std::move *//*函数模板的重载
*/
template typename T
void to_forward(T value) {std::cout 调用的左值函数 std::endl;
}template typename T
void to_forward(T value) {std::cout 调用的右值函数 std::endl;
}/*万能引用能同时传入左值引用和右值引用如果是左值T先会推导成T然后再发生引用折叠入股是右值引用则会什么都不干利用完美转发std::forward:先通过万能引用可以传入左值引用和右值引用然后通过完美转发能保留传入时候是左值引用还是右值引用的属性然后转发到对应的函数重载中
*/
template typename T
void func(T arg) {// 保留左值和右值的属性to_forward(std::forwardT(arg));
}int main() {int value 10;int value_l_refernce value;func(value); // 调用的左值引用函数func(value_l_refernce); // 调用的左值引用函数func(100); // 调用的右值引用函数int value_r_refernce 30;// 右值引用使用后之后调用这个变量都是作为左值func(value_r_refernce); // **调用的左值引用**func(std::move(value)); // 调用的右值引用return 0;
}