文章目录
前言
forward和move绝对是你想理解模板编程的第一步。
从forward和move入手可以对模板编程快速了解。也会对c++11模板编程越来越痴迷,真的是鬼才的设计。
一、左值和右值
- 左值就是=左边的值
- 右值就是=右边的值
实际区分就是当前值能否作为变量接受其他的赋值,不能就是右值。能就是左值。(实际就是看是不是有地址,左值都有自己的地址,右值是没有被分配地址空间的)
a = 12;
b = 13;
c = (a+b)
//显然a,b,c就是左值,因为可以放在左侧接受值。12,13,(a+b)显然就是右值,无法作为左侧接受值。
二、左值引用,右值引用,万能引用
1.左值引用
//就是普通的引用&
b = 100;
int &a = b;
const int &a = 100;
2.右值引用
c++11新引入的概念,目的是减少拷贝。提升效率。左值有拷贝构造和operator=,目的是深拷贝和浅拷贝,本质就是将其他值保存到自己地址里(即便浅拷贝也是将指针的地址赋值过来)。右值则正好设计的相反(也包含默认的右值移动构造函数,operator=),他出现的目的是,将对面的地址全部拷贝过来,不释放对面资源,将对面资源的地址保存到自身,清除对面保存的地址。有点类似浅拷贝,但是浅拷贝不会将对方的地址清空
//&&表示右值引用
int &&a = 100;
a = 101;
class(class& other);
class(class&& other);
class& operator=(class& other);
class& operator=(class&& other);
3.万能引用与引用折叠
- &&在模板里可以即匹配左值也可以匹配右值
- 引用折叠规则如下,就是保存原有类型。
&& & = &
&& && = && - 在模板里两个类型在一起得出一个类型时也会发生折叠
_T1& 最后都是左值引用
_T1&& 最后取决于_T1的类型。
三、forward和move
1.forward
完美转化,可以将所传入的右值传出还是右值。
v传入类型(取左值还是右值),u传出的类型(取变量类型(int,double等))。
std::forward<int>(100);
std::forward<u>(v);
写错了这部分(今天本来想仔细讲解下forward,发现这篇介绍的有问题,直接在这篇基础上改+解析)。v不起任何作用,因为源码里看已经把这个变量去啥值的影响抛出了。
实际起作用的是u,你要传出啥类型就指定啥类型(包含引用)。
这么说完可能还是不懂,为什么要这么做。看如下图(一个右值赋值给一个左值后,就会变成一个左值了,除非显示指示他是个右值,他才会是右值,因此forward对于右值如何传出右值与之类似)。
2. move
移动拷贝函数,可以让所有值都变为右值。就是上述图片实现方式。
四、走进forward和move模板编程
在看前留个疑问,找出move和forward设计方式哪里不同。以及为什么不同。我把c++11里的源码贴出。
//使用方式。
int a = 10;
std::forward<int>(a);//&&
std::forward<int&>(a);//&
std::forward<int&&>(a);//&&
std::forward<int>(10);//&&
//std::forward<int&>(10);//报错,不能将一个右值以左值转出去
std::forward<int&&>(10);//&&
std::move(100);
//forward源码
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
//为什么我发现我写的有错误了呢,因为我打算仔细讲下forward时,发现你v根本和返回类型一点关系没有,返回值之和_Ty的类型有关,我之前的误区,还好这次发现了。
//很明显发现返回值都是return static_cast<_Ty&&>(_Arg);,因此实际返回的是左值还是右值取决于_Ty,也就是你传入的是什么。
//那为什么还要设计两个,提取参数类型干什么呢。
//这就是核心问题,因为定义就是这样的。根据源码看,forward就是不能让右值以左值的性质传出。这个目前不理解。但是不妨碍我们推导一下,就两种特殊情况,左值传入可以右值传出,右值传入却不能以左值传出。区别在哪里呢。你是不是突然想明白了,你不能对一个右值强制转化为左值。但是在模板里你可以,为什么,因为你右值的参数传入就会变成左值了,因此为了维护语言,就必须加上此断言,就是不能让右值以左值的形式转出,这违背语言的特点。
//使用static_assert,我们可以在编译期间发现更多的错误,用编译器来强制保证一些契约,并帮助我们改善编译信息的可读性,尤其是用于模板的时候。
//move源码
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
测试
代码
#include <iostream>
template <class _Ty, _Ty _Val>
struct integral_constant {
static constexpr _Ty value = _Val;
using value_type = _Ty;
using type = integral_constant;
constexpr operator value_type() const noexcept {
return value;
}
_NODISCARD constexpr value_type operator()() const noexcept {
return value;
}
};
template <bool _Val>
using bool_constant = integral_constant<bool, _Val>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
template <class>
_INLINE_VAR constexpr bool is_lvalue_reference_v = false; // determine whether type argument is an lvalue reference
template <class _Ty>
_INLINE_VAR constexpr bool is_lvalue_reference_v<_Ty&> = true;
template <class _Ty>
struct is_lvalue_reference : bool_constant<is_lvalue_reference_v<_Ty>> {};
template <class>
_INLINE_VAR constexpr bool is_rvalue_reference_v = false; // determine whether type argument is an rvalue reference
template <class _Ty>
_INLINE_VAR constexpr bool is_rvalue_reference_v<_Ty&&> = true;
template <class _Ty>
struct is_rvalue_reference : bool_constant<is_rvalue_reference_v<_Ty>> {};
template <class>
_INLINE_VAR constexpr bool is_const_v = false; // determine whether type argument is const qualified
template <class _Ty>
_INLINE_VAR constexpr bool is_const_v<const _Ty> = true;
template <class _Ty>
struct is_const : bool_constant<is_const_v<_Ty>> {};
template <class _Ty>
struct remove_reference {
using type = _Ty;
using _Const_thru_ref_type = const _Ty;
};
template <class _Ty>
struct remove_reference<_Ty&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&;
};
template <class _Ty>
struct remove_reference<_Ty&&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&&;
};
template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;
//forward源码
template <class _Ty>
constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
bool flag1 = is_lvalue_reference<_Ty>::value;
bool flag2 = is_rvalue_reference<_Ty>::value;
bool flag3 = is_const<_Ty>::value;
std::cout << "左值:" << flag1
<< " :右值 " << flag2
<< " :常量 " << flag3 << std::endl;
bool flag1s = is_lvalue_reference<_Ty&&>::value;
bool flag2s = is_rvalue_reference<_Ty&&>::value;
bool flag3s = is_const<_Ty&&>::value;
std::cout << "左值:" << flag1s
<< " :右值 " << flag2s
<< " :常量 " << flag3s << std::endl;
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
bool flag1 = is_lvalue_reference<_Ty>::value;
bool flag2 = is_rvalue_reference<_Ty>::value;
bool flag3 = is_const<_Ty>::value;
std::cout << "左值:" << flag1
<< " :右值 " << flag2
<< " :常量 " << flag3 << std::endl;
bool flag1s = is_lvalue_reference<_Ty&&>::value;
bool flag2s = is_rvalue_reference<_Ty&&>::value;
bool flag3s = is_const<_Ty&&>::value;
std::cout << "左值:" << flag1s
<< " :右值 " << flag2s
<< " :常量 " << flag3s << std::endl;
return static_cast<_Ty&&>(_Arg);
}
int main(void)
{
int a = 10;
forward<int>(a);//&&
std::cout << "-------------" << std::endl;
forward<int&>(a);//&
std::cout << "-------------" << std::endl;
forward<int&&>(a);//&&
std::cout << "-------------" << std::endl;
forward<int>(10);//&&
std::cout << "-------------" << std::endl;
forward<int&>(10);//&
std::cout << "-------------" << std::endl;
forward<int&&>(10);//&&
std::cout << "-------------" << std::endl;
return 0;
}
结果
1. remove_reference引用类型擦除函数
贴出实现方式,本质就是利用模板最合适匹配规则(与重载类似)找到具体的那个然后将没有引用的基础类型定义一个新的规则,然后获取到该类型。
template <class _Ty>
struct remove_reference<_Ty&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&;
};
template <class _Ty>
struct remove_reference<_Ty&&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&&;
};
template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;
2.模板类型推导
如果对上述感觉似懂非懂,那这部分就重要了,模板推导是模板编程的基础,就跟数学公式一样。一下给出链接。
https://blog.csdn.net/songchuwang1868/article/details/90238715
3.引申 - 萃取(type_traits)
这里列一下,上述获取类型的思想都是类似于萃取的。萃取类也是模板里的常用到的。
template <class _Ty, _Ty _Val>
struct integral_constant {
static constexpr _Ty value = _Val;
using value_type = _Ty;
using type = integral_constant;
constexpr operator value_type() const noexcept {
return value;
}
_NODISCARD constexpr value_type operator()() const noexcept {
return value;
}
};
template <bool _Val>
using bool_constant = integral_constant<bool, _Val>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
本质就是对类型和值都重新定义了。将值和类型分开。具体的可以学习下迭代器的实现方式。
总结
当模板编程了解完forward,move,type_traits和模板类型推导后,建议自己学习源码这里我建议学习tuple实现以及type_traits如何实现的。搞完这些也就能入门了,我目前方向就是学完type_traits的实现。以后每天更新至少type_traits里的一个。其实他们会一个就大部分都会了,因为大概意思都是一样的,但是我建议还是自己照着手写一遍。时间长了总会入门的。