入门模板编程-forward和move

前言

forward和move绝对是你想理解模板编程的第一步。
从forward和move入手可以对模板编程快速了解。也会对c++11模板编程越来越痴迷,真的是鬼才的设计。

一、左值和右值

  1. 左值就是=左边的值
  2. 右值就是=右边的值
    实际区分就是当前值能否作为变量接受其他的赋值,不能就是右值。能就是左值。(实际就是看是不是有地址,左值都有自己的地址,右值是没有被分配地址空间的)
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.万能引用与引用折叠

  1. &&在模板里可以即匹配左值也可以匹配右值
  2. 引用折叠规则如下,就是保存原有类型。
    && & = &
    && && = &&
  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里的一个。其实他们会一个就大部分都会了,因为大概意思都是一样的,但是我建议还是自己照着手写一遍。时间长了总会入门的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值