C++ 模板进阶知识——万能引用

目录

  • [C++ 模板进阶知识------万能引用](#C++ 模板进阶知识——万能引用)
    • [1. 类型区别的基本含义](#1. 类型区别的基本含义)
    • [2. 基本认识](#2. 基本认识)
    • [3. 判断题](#3. 判断题)
    • [4. 万能引用的示例](#4. 万能引用的示例)
    • [5. 万能引用资格的剥夺与辨认](#5. 万能引用资格的剥夺与辨认)
      • [5.1 剥夺](#5.1 剥夺)
      • [5.2 辨认](#5.2 辨认)
    • 总结

C++ 模板进阶知识------万能引用

万能引用是C++11中引入的一个高级特性,它允许函数模板参数根据传入的实参自动成为左值引用或右值引用。这一特性极大地增强了模板函数的灵活性和通用性,使得编写可同时处理左值和右值的通用代码成为可能。

1. 类型区别的基本含义

看看下面这行代码:

cpp 复制代码
void func(const int &abc){}

如果问:abc是什么类型?你可以脱口而出:const int &类型。这没错,因为在代码中写着呢!

现在,把这个func()函数改造成一个函数模板:

cpp 复制代码
template <typename T> 
void func(const T &abc) { }

main()函数中添加代码调用一下:

cpp 复制代码
func(10);

现在问题来了:T是什么类型?abc是什么类型?

答案是:

  1. T的类型是int;
  2. abc的类型是const int &。

通过观察,T的类型之所以是int,是因为进行函数调用的时候给的参数是10。这是对的,但不全面。T的类型不仅取决于调用时传入的参数,还取决于abc的声明方式(即const T &)。这种类型推导是理解万能引用的关键。接下来,我们探讨当abc的类型为万能引用时如何对T的类型产生影响。

2. 基本认识

Universal Reference(万能引用)后来被称为Forwarding Reference(转发引用)。万能引用是C++11引入的一个概念,它可以根据上下文自动成为左值引用或右值引用。万能引用是一种类型,与int类型类似。右值引用用&&符号表示,主要绑定到右值上,如:

cpp 复制代码
int &&rv = 1000;

来看一个例子:

cpp 复制代码
void myfunc(int &&tmprv) 
{
    std::cout << tmprv << std::endl;
}

main()函数中添加代码:

cpp 复制代码
myfunc(10); // 正确,右值作为实参 
int i = 100; 
myfunc(i); // 错误,右值引用不能绑定左值

myfunc()函数改造成函数模板:

cpp 复制代码
template <typename T> 
void myfunc(T&& tmprv)
{ 
    std::cout << tmprv << std::endl; 
}

编译后发现myfunc(i);不再报错。原因是这里的tmprv既能接受左值,也能接受右值,这就是万能引用。

万能引用存在两种语境:

  1. 必须是函数模板;
  2. 必须发生模板类型推断,并且函数模板形参形如T&&。

万能引用的格式为T&&,它与右值引用形式相同,但解释不同。右值引用作为函数形参时,实参必须传递右值;万能引用则可以绑定左值或右值。因此,万能引用更灵活,它可以变成左值引用或右值引用。

3. 判断题

判断以下参数类型是右值引用还是万能引用:

  1. void func(int &&param){...}
    • 右值引用,因为func()不是函数模板。
  2. template<typename T> void func(T&& tmpvalue) {...}
    • 万能引用。
  3. template<typename T> void func (std::vector<T>&&param) {...}
    • 右值引用。虽然有T&&,但T被嵌套在vector中,失去了直接的类型推导,因此不是万能引用。

4. 万能引用的示例

cpp 复制代码
template <typename T>
void myfunc(T&& tmprv) 
{
    tmprv = 12; // 不管tmprv的类型是左值引用还是右值引用,都可以给tmprv赋值
    std::cout << tmprv << std::endl;
}

int main()
{
    int i = 100; 
    myfunc(i); // 左值被传递,因此tmprv是左值引用,执行完毕后i值变成12
    
    i = 200;
    myfunc(std::move(i)); // 右值被传递,因此tmprv是右值引用,执行完毕后i值变成12
}

通过以上内容,理解了万能引用的概念和应用。万能引用不仅与右值引用相似,但其存在的场景要求T是类型模板参数,并且后面跟随&&。万能引用灵活且强大,可以根据实际情况绑定到左值或右值。

5. 万能引用资格的剥夺与辨认

万能引用(Forwarding Reference)是一个强大的特性,可以在不同的上下文中表现为左值引用或右值引用。然而,某些情况下,一个本应是万能引用的表达式会失去这个资格。理解何时发生这种剥夺以及如何辨认万能引用,对于高效利用C++的模板编程至关重要。

5.1 剥夺

万能引用的资格可以被以下几种情况剥夺:

  1. 类型说明符的存在

    • 如果在类型声明中使用了constvolatile等类型修饰符,那么这个引用将不再是万能引用。例如,const T&&不是万能引用,而是一个常量右值引用。

    cpp 复制代码
    template<typename T>
    void func(const T&& param) {} // 这里的param是常量右值引用,不是万能引用
  2. 类型不直接为T&&

    • 如果类型通过别名(typedef或using)间接定义,那么它不再是万能引用。例如:

      cpp 复制代码
      template<typename T>
      using myType = T&&;
      template<typename T>
      void func(myType<T> param) {} // 这里的param不是万能引用
  3. 数组或函数类型

    • 如果T是数组或函数类型,那么即使使用了T&&的形式,也不是万能引用。

    cpp 复制代码
    template<typename T>
    void func(T&& param) {}
    // 如果T是数组类型 int arr[10]; 则func(arr)中的param不是万能引用
    // 如果T是函数类型 void f(); 则func(f)中的param不是万能引用
  4. 模板实例化时T已确定

    • 如果在模板实例化时类型T已经明确,则T&&不再具备万能引用的特性,而是变成了普通的右值引用。

    cpp 复制代码
    template<typename T>
    void func(T&& param) {}
    int main() 
    {
        int a = 10;
        func<int&>(a);  // 在这里,T被显式指定为int&,所以T&&变为int& &&,即int&
    }

5.2 辨认

要辨认一个引用是否为万能引用,可以遵循以下规则:

  1. 检查是否在函数模板中

    • 确保正在查看的代码位于模板函数中,且涉及类型推断。
  2. 直接形式为T&&

    • 引用必须直接声明为T&&,其中T是模板参数,并且没有任何修饰(如const或volatile)。
  3. 无类型别名

    • 确保T&&没有通过类型别名定义。直接使用原始形式。
  4. 模板类型参数推断参与

    • 确保在函数调用时存在模板类型参数的推断。如果模板参数在函数调用前已确定,那么该引用就可能不是万能引用。

下面是一些例子来帮助辨认:

cpp 复制代码
template<typename T>
void example1(T&& arg) { }  // 万能引用

template<typename T>
void example2(const T&& arg) { }  // 非万能引用,是const修饰的右值引用

template<typename T>
using RefType = T&&;
template<typename T>
void example3(RefType<T> arg) { }  // 非万能引用,使用了类型别名

int main() {
    int a = 10;
    example1(a);  // T被推导为int&
    example1(10); // T被推导为int
}

总结

理解万能引用的关键在于:它依赖于类型推导,可以绑定到左值或右值,并且最终会根据传入的参数类型,变成左值引用或右值引用。

相关推荐
天下皆白_唯我独黑5 分钟前
php 使用qrcode制作二维码图片
开发语言·php
夜雨翦春韭9 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
小远yyds11 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
何曾参静谧23 分钟前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
AI街潜水的八角32 分钟前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
q567315231 小时前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
JSU_曾是此间年少1 小时前
数据结构——线性表与链表
数据结构·c++·算法
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar1 小时前
yelp数据集上识别潜在的热门商家
开发语言·python