欢迎光临
我们一直在努力

模板进阶:深入理解模板机制

本文代码已同步Github

一、非类型模板参数

1. 什么是非类型模板参数

模板参数分为类型形参和非类型形参

类型形参:出现在模板参数列表中,跟在class或者typename之后的类型形参

非类型形参:用一个常量作为模板的一个参数,在模板中当成常量来使用

我们通过一个例子来理解

namespace stl
{
//N即为非类型模板参数
template<size_t N = 2>
class Stack
{
private:
int _a[N];
int _top;
int _size;
};
}

void test_template_value()
{
//通过传入的N的大小来控制底层的_a
stl::Stack<> st0;
stl::Stack<5> st1;
stl::Stack<10> st2;
}

2. 非类型模板参数的限制

  • 非类型模板参数必须在编译期就能够确定
  • 浮点数、类对象以及字符串不能作为非类型模板参数。

3、理解非类型模板参数

我们通过STL中的array来理解非类型模板参数

在这里插入图片描述

发现array就是一个标准的使用非类型模板参数实现的容器;

底层就是一个大小为N的静态数组

不放我们直接手动模拟实现一个array类,来体会非类型模板参数的应用

首先来搭建基础框架

template<class T, size_t N = 10>
class array
{
public:

private:
size_t _size;
T _array[N];//N的大小来控制静态数组的大小
};

顺便实现一下operator[]以及基础接口

T& operator[](size_t pos)
{
return _array[pos];
}

const T& operator[](size_t pos) const
{
return _array[pos];
}

size_t size()
{
return _size;
}

bool ematy()
{
return _size == 0;
}

来测试一下

在这里插入图片描述

二、模板特化

1. 为什么需要模板特化

在使用模板时,通常可以提高代码效率,简化处理;

最常见的便是比较函数模板,实现模板之后,直接传入参数即可进行比较;

template<class T>
bool less(const T& x,const T& y)
{
return x < y;
}

在这里插入图片描述

我们再来看一个列子

void test_compare()
{
int a = 1;
int b = 2;
std::cout << less(a, b) << std::endl;

std::string s1 = "hello";
std::string s2 = "world";
std::cout << less(s1, s2) << std::endl;

std::string* p1 = &s1;
std::string* p2 = &s2;
//此时比较的是什么?
std::cout << less(p1, p2) << std::endl;

}

显然,比较的是地址

模板实例化的T推导出类型为string*,因此会比较地址大小;

而我们期望的是比较指针指向的内容;

此时就需要函数模板的特化来进行处理;

2. 函数模板特化

怎么进行函数模板特化呢?

  • 必须要先有一个基础的函数模板
  • 关键字template后面接一对空的尖括号<>
  • 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
  • 对于刚才例子中出现的情况,我们进行特化

    template<class T>
    bool less(const T& x,const T& y)
    {
    return x < y;
    }

    //特化
    template<>
    bool less<std::string*>(const std::string*& x, const std::string*& y)
    {
    return *x < *y;
    }

    这样特化之后,编译器报错了:

    为什么说我们特化的版本那无法与原版本匹配呢?

    关键就在const上;

    对于函数模板,const修饰的就是变量本身; 如果x是指针,那就是修饰指针本身不被修改

    对于特化版本,const此时修饰的是指针所指向的内容;

    因此,导致两者不匹配;

    该怎么解决呢?

    在特化的版本中,将const改成修饰变量本身即可

    对于函数模板,T仅仅表示类型,x仅仅表示变量; 因此,无法将函数模板中的const改成修饰指针所指向的内容

    template<class T>
    bool less(const T& x,const T& y)
    {
    return x < y;
    }

    //特化
    template<>
    bool less<std::string*>(std::string* const & x, std::string* const & y)
    {
    return *x < *y;
    }

    3. 为什么函数模板特化不推荐

    首先,面对这种情况,我们可以直接选择实现出特定的函数即可

    bool less(std::string* const& x, std::string* const& y)
    {
    return *x < *y;
    }

    直接设计成一个普通函数;

    其次,特化实现起来有点麻烦;不如直接实现成普通函数;

    对于const的把控,也需要有一定认识;

    因此,不推荐使用函数模板特化

    4. 类模板特化

    ① 全特化

    全特化:将模板参数列表中所有的参数都确定化

    我们给出全特化的步骤:

  • 定义主模板。
  • 使用 template<>。
  • 类名后指定具体类型。
  • 重新实现成员。
  • 我们通过一个例子来理解

    template<class T1,class T2>
    class example
    {
    public:
    example()
    {
    std::cout << "example<T1, T2>" << std::endl;
    }
    private:
    T1 _a;
    T2 _b;
    };

    //全特化
    template<>
    class example<int,char>
    {
    public:
    example()
    {
    std::cout << "example<int, char>" << std::endl;
    }
    private:
    int _a;
    char _b;
    };

    我们来进行测试

    在这里插入图片描述

    ② 偏特化

    偏特化:任何针对模版参数进一步进行条件限制设计的特化版本

    我们先给出偏特化步骤

  • 前提:必须先存在一个主模板。
  • 声明:使用 template<保留的模板参数>(尖括号内写未确定的类型)。
  • 指定:类名后跟 <部分限制的类型>
  • 实现:在偏特化版本中完全重新实现所有成员
  • 对于刚才的类模板,我们进行偏特化

    //偏特化
    template<class T>
    class example<T,int>
    {
    public:
    example()
    {
    std::cout << "example<T,int>" << std::endl;
    }
    private:
    T _a;
    int _b;
    };

    除此之外,还可以进一步对参数进行限制;

    //参数偏特化为指针
    template<class T1,class T2>
    class example<T1*,T2*>
    {
    public:
    example()
    {
    std::cout << "example<T1*,T2*>" << std::endl;
    }
    private:
    T1* _a;
    T2* _b;
    };

    //参数偏特化为引用
    template<class T1, class T2>
    class example<T1&, T2&>
    {
    public:
    example(const T1& _a, const T2& _b)
    :_a(_a)
    ,_b(_b)//引用必须要初始化
    {
    std::cout << "example<T1&, T2&>" << std::endl;
    }
    private:

    const T1& _a;
    const T2& _b;
    };

    我们来测试一下

    在这里插入图片描述

    三、模板分离编译

    1. 什么是分离编译

    一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有 目标文件链接起来形成单一的可执行文件的过程称为分离编译模式

    2. 为什么模板不能正常进行分离编译

    //.h
    //声明
    template<class T>
    T Add(const T& x, const T& y);

    //.cpp
    void test_add()
    {
    //error LNK2019: 无法解析的外部符号 "int __cdecl stl::Add<int>(int const &,int const &)" (??$Add@H@stl@@YAHAEBH0@Z),函数 "void __cdecl stl::test_add(void)" (?test_add@stl@@YAXXZ) 中引用了该符号
    std::cout << Add(1, 2) << std::endl;
    //error LNK2019: 无法解析的外部符号 "double __cdecl stl::Add<double>(double const &,double const &)" (??$Add@N@stl@@YANAEBN0@Z),函数 "void __cdecl stl::test_add(void)" (?test_add@stl@@YAXXZ) 中引用了该符号
    std::cout << Add(1.1, 2.2) << std::endl;
    }

    根本原因在于:模板是编译时的“蓝图”,实例化时必须看到完整定义(声明+实现)

    正常情况下,程序运行前有以下步骤:

    预处理 -> 编译 -> 汇编 -> 链接

    在链接之前,多文件之前互不干扰;

    在链接时,编译器会去找main函数中各个函数的地址 对于普通函数,直接就能找到其地址; 而对于模板,由于声明与定义是分离的,导致只能找到声明,找不到定义;

    因此,导致最终编译失败;

    3. 如何解决

    ① 将声明和定义放在一起

    template<class T>
    T Add(const T& x, const T& y)
    {
    return x + y;
    }

    在这里插入图片描述

    ② 显式实例化

    //.h
    //声明
    template<class T>
    T Add(const T& x, const T& y);

    //显示实例化
    template int Add(const int&, const int&);
    template double Add(const double&, const double&);

    //.cpp
    //Add函数定义
    template<class T>
    T Add(const T& x, const T& y)
    {
    return x + y;
    }

    void test_add()
    {
    std::cout << Add(1, 2) << std::endl;
    std::cout << Add(1.1, 2.2) << std::endl;

    }

    在这里插入图片描述

    四、模板总结

    通过本篇的学习,我们进一步了解了模板在实际使用中的一些重要特性:

  • 非类型模板参数
    • 模板参数除了可以是类型,还可以是编译期确定的常量。
    • 进一步提高了模板的灵活性。
  • 模板特化
    • 当通用模板无法正确处理某些特殊类型时,可以针对特定类型提供特殊实现。
    • 函数模板可以进行特化,但通常直接使用普通函数会更加简单。
    • 类模板特化分为全特化和偏特化。
  • 模板分离编译
    • 模板的实例化需要看到完整的模板定义。
    • 因此,模板的声明和定义通常需要放在同一个文件中,实际开发中一般使用 .hpp 文件。
  • 模板的优缺点
    • **优点:**提高代码复用性、增强代码灵活性,减少重复代码。
    • **缺点:**可能造成代码膨胀、增加编译时间,并且模板编译错误信息通常比较复杂。
  • 通过这些内容,可以看到模板并不仅仅是“让一个函数支持多种类型”,而是一套能够根据不同需求生成和定制代码的机制。在实际开发中,需要根据具体场景合理使用模板。

    如果觉得有帮助,可以关注Github项目持续更新

    赞(0)
    未经允许不得转载:171主机测评 » 模板进阶:深入理解模板机制
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址