C++ 模板特化(Template Specialization)

C++ 模板特化(Template Specialization)

我们前面学习过 C++ 模板,它让函数或类在编译时根据传入的类型生成不同的实现,这是泛型编程的核心。可有时候,我们希望某一个特殊类型走一套不一样的逻辑。这时就要用到 模板特化(Template Specialization)。说白了,就是既保留模板的通用性,又能给特殊情况单独开个小灶,代码写起来更灵活。

1. 函数模板

#include <iostream>
#include <string>

template <class T>
std::string cast_to_string(const T& value) 
{
	return std::to_string(value);
}

void demo()
{
	int a = 10;
	std::cout << std::boolalpha << cast_to_string(a) << std::endl;

	double b = 3.14;
	std::cout << std::boolalpha << cast_to_string(b) << std::endl;

	// 问题: 我们希望 bool 类型转换为 true 或者 false,而不是 0 或者 1
	bool c = true;
	std::cout << std::boolalpha << cast_to_string(c) << std::endl;
}

int main()
{
	demo();
	return 0;
}

// 函数模板特化
template <>
std::string cast_to_string<bool>(const bool& value)
{
	return value == true ? "true" : "false";
}

// 函数模板重载
std::string cast_to_string(const bool& value)
{
	return value == true ? "true" : "false";
}

我们看到,函数模板除了写特化版本,还可以直接写一个重载函数来适配特定类型。两种方式效果一样,但重载的写法更简洁。

2. 类模板

#include <iostream>

// 1. 主模板:适用于大多数类型
template <typename T>
class Printer {
public:
    void print(const T& value) 
    {
        std::cout << value << std::endl;
    }
};

// 2. 全特化:适用于某个特定类型
template <>
class Printer<bool> {
public:
    void print(const bool value) 
    { 
        std::cout << (value == true ? "true" : "false") << std::endl;
    }
};

// 3. 偏特化:适用于部分特定类型
template <typename T>
class Printer<T*> {
public:
    void print(const T* const& value) 
    {
        if (value)
        {
            std::cout << *value << std::endl;
        }
        else 
        {
            std::cout << "nullptr" << std::endl;
        }
    }
};

int main() {

    // 使用主模板
    int a = 100;
    Printer<int>().print(a);

    // 全特化版本
    bool b = false;
    Printer<bool>().print(b);

    // 偏特化版本
    int* p = &a;
    Printer<int*>().print(p);

    return 0;
}

// 主模板
template <class T, class U>
class MyClass 
{
public:
    void print() 
    {
        std::cout << "MyClass 主模板" << std::endl;
    }
};

// 全特化
template <>
class MyClass<int, double>
{
public:
    void print()
    {
        std::cout << "MyClass 全特化" << std::endl;
    }
};

// 偏特化
template <class U>
class MyClass<int, U>
{
public:
    void print()
    {
        std::cout << "MyClass 偏特化" << std::endl;
    }
};

3. 应用场景

在 C++ 里,面对不同的数据类型,我们通常会写一堆函数重载,让同一段逻辑适配 int、double、char。

这样写代码量明显上去了。模板技术就是来解决这个问题的,我们用一个泛化的函数就能覆盖多种类型。

我们用一个虚拟类型 T 代替具体类型,就得到了一个泛化函数,它能对 int、double、char 做出正确的比较。

函数模板确实好用,但这里有个坑。如果 T 是 int* 指针类型,按照模板里 a == b 的写法,比较的就成了两个指针的地址,而不是指针指向的数据。

这个问题该怎么解决呢?

办法就是给这个模板单独写一个针对指针类型的版本,这就是函数模板特化。

那特化版本具体怎么写呢?

第 2 行那个函数模板是泛化版本,用来对付大多数类型。

第 8 行那个是特化版本,专门针对某一个具体类型。

注意:特化版本的模板参数列表是空的(一对尖括号里什么都不写),而且函数名后面还要用尖括号指明这个特化是针对哪种类型的。

我们接着看代码运行时的调用情况。

第 16 行那次调用,走的是泛化版本的 is_equal。

第 19 行那次调用,走的是特化版本的 is_equal。

另外,函数模板和普通函数是可以以重载形式共存的。

讲到这里,可能有同学会问了。我们知道普通函数重载优先级更高,能匹配上普通函数就不会走模板。既然普通函数也能解决 int* 比较,那为什么还要费劲儿写模板特化?这两者到底有什么区别?

区别在于代码实例化的时机。先说普通函数重载,不管你调没调到它,编译后它都会生成一份函数实例,一直占着代码空间。哪怕从头到尾你都没比较过 int*,这份代码也白白带在里面。

函数模板特化就不一样了。模板只有真正被调用时才生成实例,也就是说,只有特化版本被调用了,编译器才会为它生成那份代码。

当然,单纯比语法简洁,函数重载确实比重写特化省事。但要注意,类模板是没有重载这一说的,遇到需要特殊处理的类型,只能靠类模板特化来解决。

至此,关于模板特化的内容就讲完了,希望对你有帮助。