先序文章请看
C++模板元编程详细教程(之一)
C++模板元编程详细教程(之二)
模板特化
有了前两篇的基础,相信大家对模板编程已经有一点初步的感觉了。趁热打铁,这一篇我们主要来介绍一下模板特化。
首先来看一下下面的例子:
template <typename T>
void add(T &t1, const T &t2) {
t1 += t2;
}
上面是一个简单的模板函数,用于把第二个参数的值加到第一个参数中去。这个模板函数对于基本数据类型的实例化都是没什么问题的,但是如果是字符串的话,那将会有问题:
void Demo() {
int a = 1, b = 3;
add(a, b); // add<int>,调用符合预期
char c1[16] = "abc";
char c2[] = "123";
add(c1, c2); // add<char *>, 调用不符合预期
}
这里的问题就在于,对于字符串类型(这里指原始C字符串,而不是std::string)来说,「相加」并不是简单的+=,因为字符串主要是用字符指针来承载的,指针相加是不合预期的。我们希望的是字符串拼接。
因此,我们希望,单独针对于char *的实例化能够拥有不同的行为,而不遵从「通用模板」中的定义。这种语法支持就叫做「特化」,或「特例」。可以理解为,针对于模板参数是某种特殊情况下进行的特殊实现。
因此,我们在通用模板的定义基础上,再针对char *类型定义一个特化:
#include <cstring>
template <typename T>
void add(T &t1, const T &t2) {
t1 += t2;
}
template <> // 模板特化也要用模板前缀,但由于已经特化了,所以参数为空
void add<char *>(char *&t1, char *const &t2) {
// 特化要指定模板参数,模板体中也要使用具体的类型
std::strcat(t1, t2);
}
void Demo() {
int a = 1, b = 3;
add(a, b); // add<int>是通过通用模板生成的,因此本质是a += b,符合预期
char c1[16] = "abc";
char c2[] = "123";
add(c1, c2); // add<char *>有定义特化,所以直接调用特化函数,因此本质是strcat(c1, c2),符合预期
}
上例简单展示了一下模板特化目标解决的问题,和其基本的语法。但其实模板特化远不止如此,它有着巨大的潜力。
模板的特化分两种情况,一种是全特化(有的地方也叫特例),一种是偏特化(有的地方也叫部分特化)。全特化相对简单一些,笔者会先来介绍。而偏特化会伴随SFINAE理论,它将会成为模板元编程最核心的理论基础。
全特化与模板的链接方式
首先复习一下我们在开篇时候所提到的一个非常重要的概念。**模板本身不是可使用的代码,而是一种代码的升成方法。需要经过实例化后才能成为实际可用的代码。**比如说模板函数需要指定模板参数(可以是显式指定,也可以是编译器自动推导)实例化后,才能成为函数,同理,模板类也需要实例化后才能成为类。
然而「全特化」就是说,当所有模板参数都指定了的时候,才叫「全」。那么上一节中add的示例就是一个全特化,因为它原本只有一个模板参数,把它特化了自然是「完全」特化的。
而要谈到全特化,就不得不谈到一个非常容易踩坑的点,那就是模板的链接方式。在一个单独的.cpp文件中使用模板并不会有什么链接性问题,但如果在多个文件中都要使用呢?自然要通过「头文件声明+链接」的方式来完成了。
但模板本身又很特殊,它本身不是可用的代码,而是代码生成器,因此编译器会在编译期用模板来生成代码,注意,这个时候还没有开始链接!所以问题就产生了,假如我们按照直觉和习惯,把模板的声明和定义分文件来写,会怎么样呢?请看下面示例:
tmp.h
#pragma once
template <typename T>
void f(const T &t); // 声明
tmp.cpp
#include "tmp.h"
template <typename T>
void f(const T &t) {

本文深入探讨C++模板编程中的模板特化概念,包括全特化与偏特化的使用场景及链接方式,并通过示例代码讲解如何实现特定类型的定制化处理。
最低0.47元/天 解锁文章
816

被折叠的 条评论
为什么被折叠?



