C++模板进阶

SystemVerilog新手实战指南:iverilog与VScode的完美组合 本文为SystemVerilog新手提供了一套高效入门方案,详细介绍了如何利用开源编译器iverilog与轻量编辑器VScode搭建学习环境。内容涵盖十分钟快速安装配置、首个工程从编码到查看波形的完整流程,并演示了动态数组和队列等SystemVerilog新特性的使用,旨在帮助初学者避开复杂商业工具,快速掌握数字电路设计与验证的核心技能。 阅读详情

非类型模板参数

一、概念

在 C++ 模板机制中,模板参数分为类型模板参数和非类型模板参数。非类型模板参数(Non-type template parameters)允许使用除了类型之外的常量表达式作为模板的参数,例如整型常量、枚举值、指针(包括函数指针、对象指针等)、引用(左值引用)等,来在编译阶段确定模板实例化的一些特定属性。

二、语法形式

非类型模板参数的语法格式是在模板定义中,将相应的常量表达式放在模板参数列表里,形式如下:

template <typename T, int N>  // 这里的int N就是非类型模板参数,N需要是编译期可确定的整数常量
class MyClass {
    // 类的具体实现
};

在这个示例中,T 是类型模板参数,而 int N 就是非类型模板参数,它限定了 N 必须是在编译阶段就能确定值的整数类型的常量表达式。

三、使用示例

1. 数组大小指

可以利用非类型模板参数来指定数组的大小,示例如下:

#include <iostream>

template <typename T, int Size>
class ArrayWrapper {
public:
    T arr[Size];
    void print() {
        for (int i = 0; i < Size; ++i) {
            std::cout << arr[i] << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    ArrayWrapper<int, 5> myArr;  // 使用非类型模板参数指定数组大小为5
    for (int i = 0; i < 5; ++i) {
        myArr.arr[i] = i;
    }
    myArr.print();
    return 0;
}

在上述代码中:

  • 定义了一个名为 ArrayWrapper 的类模板,它有一个类型模板参数 T 用于指定数组元素的类型,还有一个非类型模板参数 int Size 用于确定数组的大小,且这个大小在编译阶段就被确定下来了。
  • 在 main 函数中,通过 ArrayWrapper<int, 5> 实例化了这个模板类,创建了一个元素类型为 int 、数组大小为 5 的对象 myArr,后续可以对这个对象进行操作,比如给数组元素赋值并打印输出。
2. 函数指针作为非类型模板参数

以下示例展示了如何将函数指针作为非类型模板参数传递,实现不同函数在编译阶段绑定到模板中:

#include <iostream>

// 两个普通函数示例
void func1(int num) {
    std::cout << "执行 func1,参数为: " << num << std::endl;
}

void func2(int num) {
    std::cout << "执行 func2,参数为: " << num << std::endl;
}

// 定义带有函数指针作为非类型模板参数的模板类
template <void (*FuncPtr)(int)>
class FunctionCaller {
public:
    static void call(int num) {
        FuncPtr(num);
    }
};

int main() {
    FunctionCaller<func1>::call(10);  // 调用绑定了func1的模板类的call函数
    FunctionCaller<func2>::call(20);  // 调用绑定了func2的模板类的call函数
    return 0;
}

在这个例子里:

  • 首先定义了两个简单的函数 func1 和 func2,它们都接受一个整数参数并输出相应的执行信息。
  • 接着定义了一个类模板 FunctionCaller,其非类型模板参数是一个指向接受整数参数、返回值为 void 的函数指针(void (*FuncPtr)(int) )。
  • 在类模板中,有一个静态成员函数 call,它通过这个函数指针来调用相应的函数。
  • 在 main 函数中,通过 FunctionCaller<func1>::call(10) 和 FunctionCaller<func2>::call(20) 这样的方式,分别将 func1 和 func2 函数绑定到模板类的实例中并进行调用,编译器在编译阶段就能确定具体调用的是哪个函数了。

四、限制与特点

  • 编译期确定值:非类型模板参数的值必须在编译阶段就能够确定,像变量(运行时才有值的)就不能作为非类型模板参数。例如,下面这种用法是错误的:
int num = 10;
template <int N = num>  // num是变量,编译阶段不能确定其值,不符合非类型模板参数要求
class ErrorClass {
    //...
};
  • 类型匹配严格:传递给非类型模板参数的常量表达式类型要和模板定义中声明的参数类型严格匹配。例如,如果模板定义中声明的是非 const 类型的参数,那么传递 const 类型的常量表达式就可能会出现编译问题(取决于具体的编译器实现和上下文情况)。
  • 增强代码复用性与灵活性:通过使用非类型模板参数,可以在不改变模板基本逻辑的情况下,根据不同的常量参数生成不同的模板实例,提高代码的复用程度,并且能在编译阶段进行一些特定的优化等操作,让代码更加灵活高效。

总之,非类型模板参数是 C++ 模板特性中一个很有用的部分,它在很多场景下能够帮助我们编写出更加灵活、高效且复用性强的代码。

 模板的特化

一、模板特化的概念

在 C++ 中,模板是一种强大的代码复用机制,它允许编写通用的代码,可以适用于不同类型的数据。然而,有时候对于特定的类型,通用模板的实现可能并不合适或者效率不高,这时候就可以使用模板特化(Template Specialization)。

模板特化是指针对特定的模板参数类型,为模板提供一个专门的、不同于通用模板实现的具体定义,这样在使用该特定类型实例化模板时,编译器就会采用特化版本的代码,而不是通用模板的代码。

二、函数模板特化

  • 示例代码及解释:
#include <iostream>
#include <string>

// 通用的函数模板
template <typename T>
void print(T value) {
    std::cout << "通用模板: " << value << std::endl;
}

// 针对 std::string 类型的函数模板特化
template <>
void print<std::string>(std::string value) {
    std::cout << "针对 string 类型的特化: " << value << std::endl;
}

int main() {
    int num = 10;
    std::string str = "Hello";
    print(num);  // 调用通用模板
    print(str);  // 调用针对 string 类型的特化模板
    return 0;
}

在上述代码中:

  • 首先定义了一个通用的函数模板 print,它可以接受任意类型 T 的参数,并输出相应的信息。
  • 接着通过 template <> 语法声明了针对 std::string 类型的特化版本。注意特化版本的函数声明中,模板参数类型要明确写在函数名后的尖括号中(如 print<std::string> ),并且函数参数的类型也要与特化的类型匹配。
  • 在 main 函数中,分别用整数和字符串类型调用 print 函数,编译器会根据传入的实际参数类型来决定是调用通用模板还是特化模板。对于整数 num,调用通用模板;对于字符串 str,则调用针对 std::string 类型的特化模板。

三、类模板特化

  • 示例代码及解释:
#include <iostream>
#include <string>

// 通用的类模板
template <typename T>
class MyClass {
public:
    void show() {
        std::cout << "通用类模板成员函数" << std::endl;
    }
};

// 针对 int 类型的类模板全特化
template <>
class MyClass<int> {
public:
    void show() {
        std::cout << "针对 int 类型的类模板全特化成员函数" << std::endl;
    }
};

// 主函数测试
int main() {
    MyClass<double> obj1;
    obj1.show();  // 调用通用类模板的成员函数

    MyClass<int> obj2;
    obj2.show();  // 调用针对 int 类型的类模板全特化的成员函数
    return 0;
}

在这个类模板相关的代码示例里:

  • 先是定义了一个通用的类模板 MyClass,它包含了一个简单的成员函数 show,用于输出通用模板相关的提示信息。
  • 然后使用 template <> 语法创建了针对 int 类型的全特化版本的 MyClass 类模板。在全特化版本中,重新定义了 show 成员函数,输出针对 int 类型特化的提示信息。
  • 在 main 函数中,分别创建了 MyClass<double> 和 MyClass<int> 类型的对象,调用它们的 show 成员函数时,编译器会根据对象的具体模板参数类型来决定调用通用模板还是特化模板对应的成员函数。

四、类模板的部分特化

  • 概念:与全特化不同,部分特化(Partial Specialization)是针对模板参数进行部分的限定,而不是像全特化那样完全指定具体的类型。通常用于在模板有多个参数时,对其中一部分参数进行特定情况的处理。
  • 示例代码及解释:
#include <iostream>
#include <vector>

// 通用的类模板,有两个模板参数
template <typename T, typename U>
class MyTemplate {
public:
    void print() {
        std::cout << "通用类模板的 print 函数" << std::endl;
    }
};

// 部分特化,针对第二个模板参数为 std::vector 类型的情况
template <typename T>
class MyTemplate<T, std::vector<T>> {
public:
    void print() {
        std::cout << "类模板部分特化(第二个参数为 vector)的 print 函数" << std::endl;
    }
};

int main() {
    MyTemplate<int, double> obj1;
    obj1.print();  // 调用通用类模板的 print 函数

    MyTemplate<int, std::vector<int>> obj2;
    obj2.print();  // 调用类模板部分特化的 print 函数
    return 0;
}

在此示例中:

  • 一开始定义了具有两个模板参数 T 和 U 的通用类模板 MyTemplate,它里面有一个 print 成员函数用于输出通用模板相关的标识语句。
  • 之后定义了部分特化版本,针对第二个模板参数是 std::vector 类型(且第一个参数 T 保持为任意类型)的情况进行了特化,在这个部分特化的类中,重新实现了 print 成员函数,输出对应特化情况的标识语句。
  • 在 main 函数中,分别创建不同类型组合的 MyTemplate 类对象并调用 print 成员函数,编译器会根据对象的模板参数是否符合部分特化的限定条件,来决定调用通用模板还是部分特化模板对应的函数。

总之,模板特化在 C++ 编程中是一个很重要的特性,它可以让我们根据具体的类型情况,对模板代码进行更精准、高效的实现,增强代码的灵活性和适应性。

 模板分离编译

一、什么是分离编译

在 C++ 编程中,分离编译(Separate Compilation)是一种常见的编译方式,它允许将程序的代码分散到多个源文件(.cpp 文件)和头文件(.h 或 .hpp 文件)中进行独立编译,最后再将各个编译单元生成的目标文件链接起来形成可执行程序。这样做的好处是便于代码的组织、维护以及团队协作开发等,不同的开发者可以并行地对不同的源文件进行开发和编译工作。

例如,一个大型项目可以有多个 .cpp 文件分别实现不同的功能模块,对应的 .h 文件用于声明这些模块中的函数、类等接口信息,编译器可以分别对这些源文件进行编译,之后再统一链接。

二、模板与分离编译的问题

对于普通的函数、类等代码,分离编译机制能够很好地运作。然而,模板却面临着特殊的情况,存在一些在分离编译下的问题。

模板在 C++ 中本质上是一种 “代码生成机制”,编译器需要根据模板被使用时的实际参数类型来生成对应的具体代码实例。在模板定义时,编译器并不知道会被用于哪些具体类型,所以只是记录下模板的定义框架,暂不生成具体的代码。

当采用分离编译时,如果模板的声明放在头文件(这是常见做法,方便其他源文件包含并使用模板相关内容),而模板的定义放在 .cpp 文件中,就会出现编译错误。例如:

my_template.h 头文件(模板声明部分):

template <typename T>
class MyTemplateClass {
public:
    void func(T value);
};

 my_template.cpp 文件(模板定义部分):

#include "my_template.h"
template <typename T>
void MyTemplateClass<T>::func(T value) {
    // 函数具体实现逻辑,比如简单的输出
    std::cout << value << std::endl;
}

main.cpp 文件(使用模板的地方): 

#include "my_template.h"
#include <iostream>

int main() {
    MyTemplateClass<int> obj;
    obj.func(10);
    return 0;
}

在上述代码结构中,尝试对 main.cpp 文件进行编译时,编译器会报错,因为它在编译 main.cpp 这个单元时,虽然通过头文件知道了 MyTemplateClass 模板类及其 func 成员函数的声明,但在对应的 .cpp 文件中模板定义部分的代码并没有被实例化(编译器不会主动去查找并实例化那里的代码,因为它不知道具体哪些类型会使用该模板),所以找不到 MyTemplateClass<int>::func 的具体实现代码,导致链接错误。

三、解决模板分离编译问题的方法

1. 包含模板定义的头文件

最常见且简单的解决办法就是将模板的定义也放在头文件中,也就是把模板的声明和定义都放在一起,让编译器在包含头文件的每个编译单元中都能看到完整的模板信息,从而可以根据实际使用情况即时生成相应的代码实例。

修改后的代码如下:

my_template.h 头文件(模板声明与定义放在一起):

#ifndef MY_TEMPLATE_H
#define MY_TEMPLATE_H

template <typename T>
class MyTemplateClass {
public:
    void func(T value) {
        std::cout << value << std::endl;
    }
};

#endif

main.cpp 文件(使用模板的地方): 

#include "my_template.h"
#include <iostream>

int main() {
    MyTemplateClass<int> obj;
    obj.func(10);
    return 0;
}

这样,编译器在编译 main.cpp 文件时,通过包含 my_template.h 头文件,就能获取到完整的模板定义和声明,顺利生成 MyTemplateClass<int> 实例对应的代码,避免了链接错误。

2. 使用显式实例化声明(Explicit Instantiation Declaration)

可以在一个单独的编译单元(通常是 .cpp 文件)中使用显式实例化声明,告诉编译器针对某些特定类型提前生成模板的具体实例代码,然后在其他使用该模板的编译单元中,编译器就能找到对应的实例代码进行链接了。

示例如下:

my_template.h 头文件(模板声明部分):

template <typename T>
class MyTemplateClass {
public:
    void func(T value);
};

my_template.cpp 文件(模板定义及显式实例化声明部分): 

#include "my_template.h"
template <typename T>
void MyTemplateClass<T>::func(T value) {
    std::cout << value << std::endl;
}

// 显式实例化声明,针对 int 类型
template class MyTemplateClass<int>;

main.cpp 文件(使用模板的地方): 

#include "my_template.h"
#include <iostream>

int main() {
    MyTemplateClass<int> obj;
    obj.func(10);
    return 0;
}

在 my_template.cpp 文件中,通过 template class MyTemplateClass<int>; 语句,明确指示编译器生成 MyTemplateClass 模板类针对 int 类型的实例代码,这样在编译 main.cpp 文件并链接时,就能找到对应的代码进行正确链接了。

不过这种方法需要手动对每个需要实例化的类型进行显式声明,如果模板要应用于很多不同类型,操作会相对繁琐一些。

总之,C++ 模板的分离编译需要特别注意处理方式,以确保编译器能够正确地生成和链接模板相关的代码,避免出现编译或链接错误,以上介绍的方法可以根据具体的编程场景和需求来选择使用。

Isaac Sim/Lab & ROS2 入门教程 —— 宇树人形机器人 H1 强化学习策略部署 本文介绍了如何通过ROS2和IsaacSim运行H1人形机器人的强化学习运动策略。主要内容包括:1)设置机器人关节配置和初始位置;2)添加IMU传感器;3)配置ROS2节点以发布观测数据和接收动作指令;4)创建模拟环境并发布ROS时钟;5)最终运行ROS2策略实现对机器人的键盘控制。文中详细说明了关节参数设置、传感器配置、ROS2节点创建等关键步骤,并提供了注意事项和验证方法。完成配置后,用户可通过键盘指令控制机器人在平坦地形上的运动。 阅读详情

相关推荐

如何在NVIDIA CX6 网卡和华为CE交换机上为RDMA协议使能DCQCN和PFC

【代码】如何在NVIDIA CX6 网卡和华为CE交换机上使能DCQCN和PFC。

Markchen7788的博客 5093

【CPP】C++模板:初阶到进阶语法与实用编程示例

探索C++模板的深度与广度,《C++模板:初阶到进阶语法与实用编程示例》带您从基础语法起步,逐步深入到高级特性。本博客结合精炼的理论知识和丰富的代码实例,揭示模板编程的精髓,助您提升开发效率,优化代码结构。无论您是初学者还是资深开发者,都能在这里找到提升C++编程技能的宝贵资源。

BinaryWiker的博客 1380

单波束与多波束声呐:从原理到应用场景的全面解析

本文全面解析了单波束与多波束声呐的工作原理、核心特点及应用场景。单波束声呐如精准的狙击手,适合高精度需求;多波束声呐则像高效的散弹枪,适用于快速测绘和大面积覆盖。文章还提供了选型指南和前沿发展趋势,帮助用户根据预算和需求选择最适合的声呐技术。

weixin_30872157的博客 220

【C++】模板进阶---cpp模板的深入探索

模板参数分类类型形参与非类型形参。 类型形参,即出现在模板参数列表中,跟在class或者typename之类的参数类型名称。 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

专注于前后端学习 1235

计算机网络-自顶向下方法 第三章课后习题答案(第七版)

复习题 R1. a) 就叫这个协议为简单传输协议STP(Simple Transport Protocol)。在发送方,STP从发送进程接收不超过1196字节的数据块、目标主机地址和目标端口号。STP向每个块添加一个4字节的头,并将目标进程的端口号放在头中。STP然后将目标主机地址和结果段提供给网络层。网络层将段交付给目的地主机的STP。目的主机基于STP检查段中的端口号,从段中提取数据,并将数据传递给由端口号标识的进程。 b) 在发送方,STP从发送进程接收不超过1192字节的数据块、目标主机地址和目标端

Erekilu的博客 2万+

光伏发电系统模拟及其发电预测开源python工具pvlib

本文介绍光伏发电系统模拟及其发电预测开源python工具pvlib,以及举例介绍获取辐照度方法、光伏发电预测思路。

肖永威的专栏 1万+

【C++】模板进阶

这篇文章讲解了模板的进阶用法,以及模板不建议分离编译的原因

EWIAW_ilove的博客 1270

终于找到两个稳定好用的Docker镜像加速源,拉取速度提升几十倍!

国内使用Docker经常遇到镜像拉取慢、超时失败的问题🐳 本文分享两个亲测好用的免费镜像加速源⚡ 实测拉取速度从十几分钟缩短到几十秒,稳定性接近100%📊 支持一键配置脚本,支持多平台使用✨

XiaoqiangClub的博客 953

C++之模板进阶

本文深入探讨了C++模板的高级应用,主要包括三个方面:非类型模板参数、模板特化和模板分离编译。首先分析了非类型模板参数的概念和使用场景,通过与#define宏定义的对比,说明了非类型模板参数在泛型编程中的优势。其次详细讲解了模板特化的概念和实现方法,包括函数模板特化和类模板特化,并通过实例展示了全特化和偏特化的不同应用。最后,文章还简要提到了模板分离编译的相关内容。这些高级模板技术为C++泛型编程提供了更灵活的实现方式,能够更好地处理特殊类型和复杂场景。文章通过具体代码示例帮助读者理解这些抽象概念的实践应用

2401_84420653的博客 1810

15、远程操作水下航行器(ROV)的非线性控制系统设计

本文探讨了远程操作水下航行器(ROV)的非线性控制系统设计,重点介绍了多变量PID控制、改进的PD控制和滑模控制三种方法。详细阐述了各控制策略的原理、建模与仿真过程,并通过对比分析其在稳态误差、振荡、抖振及跟踪效果等方面的性能表现。文章还提出了针对不同控制方法的优化建议,并给出了选择合适控制策略的决策流程,为ROV高精度运动控制提供了系统性解决方案。

sun99的博客 157

理想L9高通QAM8797座舱域控制器全拆解|全网独家复现 硬件架构升级、电源散热优化、多屏低延迟交互、车机系统赋能高端智能座舱落地

随着智能汽车向舱驾一体、全域智能化、多模交互方向迭代,传统单一功能座舱芯片已无法满足多屏高刷渲染、全车AI语音、车内视觉感知、高速车载通信的叠加算力需求。2026款全新理想L9作为旗舰级智能SUV,全面搭载高通新一代旗舰座舱域控制器QAM8797,搭配自研马赫M100 AI芯片、国产车规主控芯片,构建起高性能、高可靠、低延迟的车载中央计算平台。

getusushu的博客 380

【C++】模板深入进阶

带你深入进阶模板,分离编译和特化,非类型模板参数,快来!

有梦想的电信狗 1551

智能体异常行为检测-基于行为序列的异常模式识别技术【附代码】

在多智能体系统(Multi-Agent Systems, MAS)中,智能体之间的行为协调与自主决策是系统稳定运行的关键。然而,当某个智能体因策略偏移、环境变化或潜在攻击而出现**异常行为(Abnormal Behavior)**时,整个系统可能陷入混乱或崩溃。

一键难忘的博客 1144

C++:模板进阶与继承

C++:模板进阶与继承

2301_76269963的博客 1338

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

【c++】模板进阶

本文主要介绍了C++模板进阶的相关知识点。首先讲解了非类型模板参数的概念,通过静态栈示例说明其优势;其次详细介绍了模板特化技术,包括函数模板特化和类模板特化(全特化与偏特化),并通过示例演示了特化的语法和使用场景;最后分析了模板声明与定义不能分离的原因,指出这是由于编译机制导致的模板实例化问题。文章通过比喻和代码示例,帮助读者理解模板特化的优先级规则和实际应用场景。

mo_sheng_sli的博客 850

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

深入理解模板进阶:掌握C++模板的高级技巧

C++模板进阶,全特化,偏特化,非类型模板参数,模板的分离编译

2301_77509762的博客 1640

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

C++ 模板进阶

return 0;1>test.obj : error LNK2019: 无法解析的外部符号 "int __cdecl Add<int>(int const &,int const &)" (??$Add@H@@YAHABH0@Z),函数 _main 中引用了该符号。

weixin_49623095的博客 1116

【c++】模板进阶,模板的分离编译重点讲解

一、class和typename作为模板参数的关键字的区别 二、非类型模板参数 介绍 使用 array容器的使用 array容器与c语言数组的对比 三、模板的特化 概念引入 四、函数模板特化 五、类模板特化 全特化 偏特化 部分特化 参数更进一步限制 测试 类模板特化的应用举例 六、模板的分离编译 什么是分离编译 通常来讲模板为什么不能声明和定义分离 怎么样模板才能进行分离编译 方法一 方法二 七、模板的总结

重铸自身根基的博客 2184

『C++ - 模板』之模板进阶

模板的优缺点优点模板采用了泛型编程,可以进行代码的复用从而可以高效的进行开发;模板增强了代码的灵活性;缺点模板的使用将会造成代码膨胀;即在原来的编译过程中只需要进行语法的判断,而对于模板而言则多增加了一个步骤为模板的实例化;不同的类型实例化出的代码也不同从而导致代码膨胀,同时也导致了编译时间变长;当使用模板进行开发时,若是出现模板的编译错误,则大量的错误信息使在开发过程中不能精确找到错误位置;

小侯宝的博客 415
上一篇: C++将整形数据转换为字符串
下一篇: C++map的详解和常用接口
Arcane Code
博客等级 码龄3年 598粉丝 101原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值