当前位置: 首页 > news >正文

C++11之可变参数模板

C++11

    • 一、可变参数模板的基本概念
    • 二.参数包扩展
      • 递归包展开
        • 参数包展开的原理
          • 参数包的定义
          • 参数包的展开
      • 另类包展开
        • 参数包展开的原理
        • 参数包的定义
        • 参数包的展开
  • 三.emplace系列接口
    • 3.1emplace出现的原因
    • 3.2`emplace` 系列接口的登场
    • 3.3`emplace` 系列接口的原理
    • 3.4通过代码来看一看emplace的效率
  • 四.新的类功能
    • 4.1 默认的移动构造和移动赋值
    • 4.2成员变量声明时给缺省值
    • 4.3defult和delete
    • 4.4final与override
      • 1. **final 关键字**
      • 2. **override 关键字**

在这里插入图片描述

在 C++ 的世界里,模板机制一直是提升代码复用性和泛型编程能力的重要工具。而可变参数模板(Variadic Templates)作为 C++11 引入的一项强大特性,更是将模板的灵活性推向了新的高度。它允许我们编写能够处理任意数量、任意类型参数的函数和类,极大地增强了代码的通用性和适应性。

一、可变参数模板的基本概念

在传统 C++ 中,函数的参数数量和类型是固定的,这在很多情况下限制了函数的通用性。而可变参数模板的出现,打破了这一限制。它允许函数接受不确定数量的参数,这些参数可以是任意类型,从而让函数能够以更加灵活的方式处理各种不同的输入。

可变参数模板的核心是使用 ...(三个点)来表示参数包(parameter pack)。参数包可以看作是一个包含了多个参数的集合,这些参数在函数中可以被逐一处理。例如,以下是一个简单的可变参数模板函数的声明:

template<typename... Args>
void print(Args... args);

在这个例子中,Args... args 就是一个参数,包Args 是一个模板参数列表,它代表了参数的类型,而 args 是参数包的名称。这个函数可以接受任意数量和类型的参数,这些参数在函数体内将被统一处理。

二.参数包扩展

递归包展开

void ShowList()
{cout << endl;
}template <class T, class ...Args>
void ShowList(T x, Args... args)
{cout << x << " ";ShowList(args...);
}template <class ...Args>
void Print(Args... args)
{// N个参数,第一个传给x,剩下N-1参数传给ShowList的第二个参数包ShowList(args...);
}

这段代码实现了一个递归打印的功能。Print 函数接收一个参数包 args,然后通过 ShowList(args...) 的方式将参数包展开,并将展开后的结果传递给 ShowList 函数。ShowList 函数通过递归的方式依次打印每个参数,直到参数包为空。

参数包展开的原理
参数包的定义

在我们的示例中,template <class ...Args> 定义了一个参数包 Args,它表示 Print 函数可以接受任意数量和类型的参数。

参数包的展开

参数包展开是指将参数包中的每个参数依次展开,以便在代码中对每个参数进行操作。展开操作是通过 ... 运算符来完成的。

Print 函数中,ShowList(args...) 是参数包展开的关键。这里,args 是参数包,ShowList(args...) 的作用是将参数包中的每个参数依次传递给 ShowList 函数。

例如,如果调用 Print(1, 2.5, "hello"),那么参数包 args 包含三个参数:12.5"hello"ShowList(args...) 的展开过程如下:

  • 第一次调用 ShowList(1, 2.5, "hello")Tintx1,剩下的参数包 args2.5"hello"
  • 第二次调用 ShowList(2.5, "hello")Tdoublex2.5,剩下的参数包 args"hello"
  • 第三次调用 ShowList("hello")Tconst char*x"hello",剩下的参数包 args 是空的。
  • 最后调用 ShowList(),参数包为空,打印换行符并结束递归。

另类包展开

首先,让我们来看一下本文的核心代码示例:

const T& GetArg(const T& x)
{cout << x << " ";return x;
}template <class ...Args>
void Arguments(Args... args)
{}template <class ...Args>
void Print(Args... args)
{// 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments// GetArg的返回值组成实参参数包,传给ArgumentsArguments(GetArg(args)...);
}

这段代码中,Print 函数是关键。它接收一个参数包 args,然后通过 GetArg(args)... 的方式将参数包展开,并将展开后的结果作为参数传递给 Arguments 函数。

参数包展开的原理
参数包的定义

template <class ...Args> 定义了一个参数包 Args,它表示 Print 函数可以接受任意数量和类型的参数。

参数包的展开

参数包展开是指将参数包中的每个参数依次展开,以便在代码中对每个参数进行操作。展开操作是通过 ... 运算符来完成的。

Print 函数中,GetArg(args)... 是参数包展开的关键。这里,args 是参数包,GetArg(args) 表示对参数包中的每个参数调用 GetArg 函数。... 运算符的作用是将 GetArg(args) 对每个参数的调用结果展开成一个参数列表,然后将这个参数列表传递给 Arguments 函数。

例如,如果调用 Print(1, 2.5, "hello"),那么参数包 args 包含三个参数:12.5"hello"GetArg(args)... 的展开过程如下:

  • 对第一个参数 1 调用 GetArg(1),得到返回值 1
  • 对第二个参数 2.5 调用 GetArg(2.5),得到返回值 2.5
  • 对第三个参数 "hello" 调用 GetArg("hello"),得到返回值 "hello"

最终,GetArg(args)... 展开成 GetArg(1), GetArg(2.5), GetArg("hello"),这相当于 1, 2.5, "hello",然后将这个参数列表传递给 Arguments 函数。

三.emplace系列接口

template <class... Args> void emplace_back (Args&&... args);template <class... Args> iterator emplace (const_iterator position, Args&&... args);
  1. C++11以后STL容器新增了empalce系列的接⼝,empalce系列的接⼝均为模板可变参数,功能上兼容push和insert系列,但是empalce还⽀持新玩法,假设容器为container,empalce还⽀持直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。

  2. emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列

  3. 传递参数包过程中,如果是 Args&&… args 的参数包,要⽤完美转发参数包,⽅式如下std::forward(args)… ,否则编译时包扩展后右值引⽤变量表达式就变成了左值。

3.1emplace出现的原因

emplace 出现之前,我们往容器里添加元素时,常常会遇到一些效率问题。比如,当我们使用 std::vectorstd::map 等容器时,如果要添加一个对象,通常的做法是先构造好对象,然后再将其拷贝或移动到容器中。这个过程涉及到临时对象的创建、拷贝或移动构造等操作,不仅代码显得繁琐,还可能带来不必要的性能开销,尤其是在处理大量数据或复杂对象时,这种开销会更加明显。

3.2emplace 系列接口的登场

C++11 引入了 emplace 系列接口,为容器操作带来了革命性的变化。emplace 的基本思想是在容器分配的存储空间上直接构造对象,从而避免了临时对象的创建和拷贝/移动构造等中间步骤。

std::vector 为例,其 emplace_back 方法允许我们在向量的末尾直接构造一个对象。假设我们有一个类 Person,包含姓名和年龄两个成员变量,我们想往 std::vector<Person> 中添加一个 Person 对象。传统的做法可能是这样:

std::vector<Person> people;
Person p("Alice", 25);
people.push_back(p);

这里,Person 对象 p 先被构造出来,然后通过 push_back 方法拷贝到 std::vector 中,涉及到两次构造(一次是 p 的构造,一次是拷贝构造到向量中)。

而使用 emplace_back,我们可以这样写:

std::vector<Person> people;
people.emplace_back("Alice", 25);

在这个例子中,emplace_back 直接在 std::vector 分配的存储空间上构造了一个 Person 对象,构造参数直接传递给 Person 的构造函数。这样,就避免了临时对象 p 的创建以及后续的拷贝构造,大大提高了效率。

3.3emplace 系列接口的原理

emplace 系列接口的魔法在于它利用了完美转发和变长参数模板。当调用 emplaceemplace_back 等方法时,这些方法会将传入的参数完美转发给容器中元素类型的构造函数,从而在容器分配的内存空间上直接构造对象。

std::mapemplace 方法为例,其大致实现原理如下:

template <typename Key, typename T, typename Compare, typename Allocator>
template <typename... Args>
std::pair<typename std::map<Key, T, Compare, Allocator>::iterator, bool>
std::map<Key, T, Compare, Allocator>::emplace(Args&&... args)
{// 在合适的位置分配内存auto position = ...;// 完美转发参数给键值对的构造函数,在分配的内存上直接构造对象auto result = emplace_hint(position, std::forward<Args>(args)...);return result;
}

这里的关键是 std::forward<Args>(args)...,它利用完美转发将参数原封不动地转发给元素类型的构造函数,从而保证了参数的值类别(左值或右值)不会改变,使得构造函数能够根据参数的实际类型进行正确的构造。

3.4通过代码来看一看emplace的效率

int main()
{// 效率用法都是一样的bit::list<int> lt1;lt1.push_back(1);lt1.emplace_back(2);bit::list<bit::string> lt2;// 传左值效率用法都是一样的bit::string s1("111111111");lt2.push_back(s1);lt2.emplace_back(s1);cout << "*****************************************" << endl;// 传右值效率用法都是一样的bit::string s2("111111111");lt2.push_back(move(s2));bit::string s3("111111111");lt2.emplace_back(move(s3));cout << "*****************************************" << endl;// emplace_back的效率略高一筹lt2.push_back("1111111111111111111111111111");lt2.emplace_back("11111111111111111111111111");cout << "*****************************************" << endl;
}

四.新的类功能

4.1 默认的移动构造和移动赋值

  1. 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷⻉构造函数/拷⻉赋值重载/取地址重载/const取地址重载,最后重要的是前4个,后两个⽤处不⼤,默认成员函数就是我们不写编译器会⽣成⼀个默认的。C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。

  2. 如果你没有⾃⼰实现移动构造函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意⼀个。那么编译器会⾃动⽣成⼀个默认移动构造。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调⽤移动构造,没有实现就调⽤拷⻉构造。

  3. 如果你没有⾃⼰实现移动赋值重载函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意⼀个,那么编译器会⾃动⽣成⼀个默认移动赋值。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调⽤移动赋值,没有实现就调⽤拷⻉赋值。(默认移动赋值跟上⾯移动构造完全类似)

  4. 如果你提供了移动构造或者移动赋值,编译器不会⾃动提供拷⻉构造和拷⻉赋值。

4.2成员变量声明时给缺省值

成员变量声明时给缺省值是给初始化列表⽤的,如果没有显⽰在初始化列表初始化,就会在初始化列表⽤这个缺省值初始化

4.3defult和delete

  1. C++11可以让你更好的控制要使⽤的默认函数。假设你要使⽤某个默认的函数,但是因为⼀些原因这个函数没有默认⽣成。⽐如:我们提供了拷⻉构造,就不会⽣成移动构造了,那么我们可以使⽤default关键字显⽰指定移动构造⽣成。

  2. 如果能想要限制某些默认函数的⽣成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他⼈想要调⽤就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指⽰编译器不⽣成对应函数的默认版本,称=delete修饰的函数为删除函数。

4.4final与override

1. final 关键字

  • 修饰类:当一个类被final修饰时,意味着这个类不能被继承。
    class Base final { /* ... */ };
    class Derived : public Base { /* 错误:Base 是 final 类,不能被继承 */ };
    
  • 修饰虚函数:若虚函数被final修饰,那么在派生类中就不能再对该函数进行重写。
    class Base {
    public:virtual void func() final; // 声明为 final
    };class Derived : public Base {
    public:void func(); // 错误:不能重写 final 函数
    };
    

2. override 关键字

在C++里,override用于显式表明派生类中的函数是对基类虚函数的重写。如果使用了override却没有成功重写基类虚函数,编译器就会报错。

class Base {
public:virtual void func(int x);
};class Derived : public Base {
public:void func(int x) override; // 正确:重写基类虚函数void func(double x) override; // 错误:基类中没有匹配的虚函数
};

在这里插入图片描述

http://www.dtcms.com/a/295659.html

相关文章:

  • ac日志报ARP-neighbor-failed问题定位过程
  • langchain+本地embedding模型+milvus实现RAG
  • ChatGPT Agent架构深度解析:OpenAI如何构建统一智能体系统
  • 青少年编程学习的新选择——《CCF GESP 直通车》与《GESP 编程能力等级认证一本通》深度剖析
  • 根据字符串数组的顺序重新排序 List顺序
  • 中国历史朝代顺序以及朝代歌
  • 核心数据结构:DataFrame
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-18,(知识点:传输线阻抗匹配方式)
  • OpenAI最新大模型GPT-4o体验之Code Copilot AI编程大模型
  • 电子书转PDF格式教程,实现epub转PDF步骤
  • Java 大视界 -- Java 大数据在智能家居能源管理与节能优化中的深度应用(361)
  • 多模态数据处理系统:用AI读PDF的智能助手系统分析
  • Maven Scope标签:解锁Java项目依赖管理的秘密武器
  • 安全逆向工程学习路线
  • 《Maven 核心基础笔记(第一天)》
  • 使用maven-shade-plugin解决依赖版本冲突
  • gitlab使用 备份恢复 全量迁移
  • 《从点击到共鸣:论坛前端如何用交互细节编织用户体验》
  • window下lua解释器安装并配置vscode环境
  • 【Practical Business English Oral Scene Interpretation】入职面试No.5~7
  • 承担CANOPEN转PROFINET协议转换功能的网关与台达伺服器的连接
  • 80道面试经典题目
  • 循环神经网络(RNN)详解:从原理到实践
  • rust-结构体使用示例
  • Elasticsearch + Logstash + Kibana搭建
  • 2025年Gtest全球软件测试技术峰会定档
  • 【二维vector遍历】 auto表示vector<int>
  • 【大模型论文阅读】2503.01821_On the Power of Context-Enhanced Learning in LLMs
  • 【论文阅读+复现】LayoutDM: Transformer-based Diffusion Model for Layout Generation
  • 使用 Python 将 CSV 文件转换为带格式的 Excel 文件