首页 > 编程语言 >C++范型二:右值引用

C++范型二:右值引用

时间:2024-10-31 17:34:02浏览次数:1  
标签:范型 tmp 右值 int C++ 引用 && 模板

为类所设计的转移语义拷贝构造函数转移语义赋值运算符使得临时对象有了将资源直接转移给另一个对象的能力,从而避免了内存分配、资源拷贝等深拷贝过程

作为注重效率的模板,当然要引入右值引用及相关技术,其成果就是参数完美转发模板

右值引用

左值和右值
左值代表一块存储空间,可以接收和保存数据,而右值仅代表数据,是一个临时对象

在C++11之前
左值可以定义两种引用,即T& t=lvalue;const T& t=lvalue;
对于右值,仅定义了一种常引用,即const T& t=rvalue;

C++11为了支持转移语义,提出新的数据类型:右值引用T&& t=rvalue;

右值引用的应用

转移语义

类的拷贝构造函数和赋值运算符重载,这两类函数具有资源控制权转移功能,所以称这类函数可以转移语义

class:
  Foo(Foo&& r){}

main:
  Foo foo(func());

转移函数move()

看到了右值引用的好处,左值也想借鉴
C++11推出了函数T&& move(T&);,接收一个参数,返回该变量的右值引用

move()使用得当,效果是巨大的,如STL中的数据交换函数std::swap()

void swap(T& a, T& b){
    T tmp= move(a); // 调用转移语义拷贝构造函数
    a= move(b);
    b= move(tmp);
}

T是一个包含大量资源的类,那这个操作将会大大提高效率

参数完美转发模板

参数完美转发问题

函数模板中调用外部函数,此时需要用模板传递参数,该行为称为参数转发
在参数转发时要求:一不能改变参数特性,二不能产生额外开销
C++11之前,模板参数转发问题一直没有得到很好的解决,主要原因是右值参数经模板转发后变成左值

void func(int v){}

template<typename T>
void tmp(T a){ func(a); }

int main(){
    int x=0;
    tmp(x);
    return 0;
}

该转发是不完美的,因为是值传递,在转发过程中需要创建临时对象并对数据进行拷贝

如下代码,改成引用传递

void func(int v){}

template<typename T>
void tmp(T& a){ func(a); }

int main(){
    int x=0;
    tmp(x);
    tmp(10); // 右值参数
    return 0;
}

该转发仍不是完美转发,因为不能进行右值参数转发,若采用左值和右值都能接收的常量左值引用const T&,则会不满足某些应用场景

可知,能完美转发的必须是右值引用

void func(int& v){}

template<typename T>
void tmp(T&& a){ func(a); }

int main(){
    int x=0;
    tmp(x);
    tmp(10); // 右值参数
    return 0;
}

C++11在推出右值引用后,做了两件事
1.制定了相应的引用类型&推导规则
2.开发与move()功能类似的forward(),以将被模板转换成左值的右值参数再转换回来

引用符&折叠规则

引用符&折叠规则,即模板参数类型推导规则
为了使编译器能正确处理多个引用符&连用的参数类型,C++11为模板定义了新的引用类型推导规则
支持C++11的编译器在模板的参数推导中一旦发现具有多个引用符的表达式时,便会按照新定义的规则将多余的引用符截掉

引用符的折叠规则reference collapsing rule

- - 折叠结果
T& T& T&
T&& T& T&
T& T&& T&
T&& T&& T&&

如下面定义多个引用符的情况

typedef int& LR;
typedef int&& RR;

int a= 10;
LR b= a;

LR& c= a;  // & &叠加
LR& c= 10; // 编译错误

LR&& f= a;  // & &&叠加
LR&& f= 10; // 编译错误

RR& g= a;  // && &叠加
RR& g= 10; // 编译错误

RR d= 10;   // 右值引用
RR&& e= 10; // && &&叠加
RR&& e= a;  // 编译错误

forward()函数

对于以右值引用T&&作为参数的函数模板,其参数推导有以下两条特殊规则

实参类型 模板形参 类型推导结果
左值lvalue T&& T&
右值rvalue T&& T
void func(int& v){ std::cout<<"call &"; }
void func(int&& v){ std::cout<<"call &&"; }

// 转发模板
template<typename T>
void tmp(T&& a){
    func(a);
}

int main(){
    int x=1;
    int& y=x;
    tmp(x);   // call &
    tmp(y);   // call &
    tmp(100); // call &
    return 0;
}

可以看出,当传入右值100时,没能正确调用预期函数,因为模板将右值转换成了左值,正确做法如下:

template<typename T>
void tmp(T&& a){
    if(a is rvalue){
        func(move(a));
    }else{
        func(a);
    }
}

move()功能单一,仅能将T&类型转为T&&,所以为了避免if-else结构,可使用下面的定义

template<typename T>
void tmp(T&& a){
    func(static_cast<T&&>(a));
}

由引用符折叠规则可知,此时能得到正确的参数转发

使用static_cast<>保证了参数的输入类型与函数的参数类型完全一致
收到的是右值,发到函数的一定是右值,收到的是左值,发到函数的一定是左值
其实,static_cast<>仅对参数为右值时有用,当模板输入的参数为左值时,类型被推导为T&&&,即T&

为了区别于move()static_cast<>,并使其更具有语义性
C++11将static_cast<>封装成函数模板std::forward(),所以可以用下面的代码实现完美转发

template<typename T>
void tmp(T&& a){
    func(forward<T>(a));
}

一个完美转发函数模板的应用实例

#include<iostream>
void func(int& x){std::cout<<"lvalue"<<std::endl;}
void func(const int& x){std::cout<<"const rvalue"<<std::endl;}
void func(int&& x){std::cout<<"rvalue"<<std::endl;}
void func(const int&& x){std::cout<<"const rvalue"<<std::endl;}

// 完美转发模板
template<typename T>
void funcT(T&& a){
    func(std::forward<T>(a));
}

int main(){
    // 右值
    funcT(10); // rvalue

    // 左值
    int a;
    funcT(a); // lvalue

    // 右值
    funcT(std::move(a)); // rvalue

    // 左值常量
    const int b=8;
    funcT(b); // const lvalue

    // 右值常量
    funcT(std::move(b)); // const rvalue

    return 0;
}

根据模板函数的参数(T&&类型)推导规则和引用符折叠规则,实现了参数的完美转发,解决了C++长久以来为人诟病的临时对象效率问题

在支持C++11的STL库中,有大量完美转发的应用,如make_pair()make_unique()

标签:范型,tmp,右值,int,C++,引用,&&,模板
From: https://www.cnblogs.com/sgqmax/p/18518490

相关文章

  • C++范型三:数据类型表
    类的数据类型成员C++中,在类模板中用typedef定义的数据类型称为内嵌类型nestedtypetemplate<typenameT>classMyTraits{public:typedefTmytype;};如下,使用内嵌类型在类外定义变量typenameMyTraits<int>::mytypen;可知,类外引用类模板的公有类型成员和引用类静......
  • C/C++中的指针详解(重点)
    指针是C和C++中一个重要且强大的特性。它们允许程序员直接访问和操作内存,提供了灵活的内存管理和高效的数据结构实现。对一个变量取*操作其实就是取到这个变量的地址,然后再对取到的变量进行读写等操作以下是对指针的详细介绍:1.什么是指针指针是一个变量,它存储另一个变量的......
  • 【C++】01-C++ 程序的生成过程
    概要:该篇文章以MSCV为例,简要介绍了C++程序的生成过程。1.生成工具MSVC,全称MicrosoftVisualC++,是由微软开发的用于生成C++程序的工具集,包括C++预处理器、编译器、链接器和其他生成工具。2.生成过程2.1预处理(Preprocess)预处理由预处理器(Preprocessor)......
  • C++ halcon判断图像是否为空
    使用CountObj函数首先,可以使用CountObj函数来检查当前图像是否为空。如果图像为空,则返回的计数为0;否则,返回的计数大于0。示例代码如下:cppCopyCode HTuplehNum;HalconCpp::CountObj(m_hCurrentImg,&hNum);if(hNum==0){qDebug()<<"传入图像为空!";}else{qDebu......
  • 【C++】红黑树的插入与删除
    第一篇数据结构学习之红黑树的实现系列文章目录前言一、红黑树的基本概念二、参考视频链接三、代码实现1.定义节点类2.旋转方法3.红黑树插入操作4.红黑树删除操作四,总体代码总结系列文章目录第一篇数据结构学习之红黑树的实现前言红黑树是一种平衡二叉搜索树,在......
  • C++——写一函数,将一个3x3的整型矩阵转置。用指针或引用方法处理。
    没注释的源代码#include<iostream>usingnamespacestd;voidmove(int*p);intmain(){  inta[3][3],*p;  cout<<"pleaseinputmatrix:"<<endl;  for(inti=0;i<3;i++)  {    for(intj=0;j<3;j++)    {     ......
  • C++——将一个5x5的矩阵中最大的元素放在中心,4个角分别放4个最小的元素(按从左到右、
    没注释的源代码#include<iostream>#include<stdio.h>#include<string.h>usingnamespacestd;voidtransform(int*arry,intcol_row);intmain(){   intarry[5][5];   cout<<"Pleaseentera5x5matrix:"<<endl;   for(......
  • C++(std::to_string())
    目录1.函数定义2.示例代码3.内部实现机制4.注意事项5.应用场景6.使用std::ostringstream控制精度的示例7.总结std::to_string()是C++11引入的一个标准库函数,用于将基本数据类型(如整数、浮点数等)转换为对应的字符串格式。这个函数属于<string>头文件,因此使用时需......
  • 【C++】深究类型转换
    ⭐️个人主页:@小羊⭐️所属专栏:C++很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎~目录一、类型转换1、C语言中的类型转换2、C++中的类型转换3、C语言类型转换的缺陷4、C++中的四种强制类型转换4.1static_cast4.2reinterpret_cast4.3const_cast4.4dynam......
  • c++时间形式转换
    https://cplusplus.com/reference/ctime/先放上官方文档。ctime类里,有很多转换时间格式的方法,下面只举例将UTC时间,转换为字符串的代码。‌‌Unix时间‌,也称为‌POSIX时间,是UNIX或类UNIX系统使用的时间表示方式。它从协调世界时1970年1月1日0时0分0秒起至现在的总秒数,不考虑闰秒......