类模板专业化
在函数模板专业化中,我们了解了如何专门化函数以便为特定数据类型提供不同的功能。事实证明,它不仅可以专门化功能,还可以专门化整个类!
考虑您想要设计一个存储8个对象的类的情况。这是一个简化的类:
template <class T>
class Storage8
{
private:
T m_array[8];
public:
void set(int index, const T &value)
{
m_array[index] = value;
}
const T& get(int index)
{
return m_array[index];
}
};
因为这个类是模板化的,所以它适用于任何给定的类型:
#include <iostream>
int main()
{
// 为整数定义Storage8
Storage8<int> intStorage;
for (int count=0; count<8; ++count)
intStorage.set(count, count);
for (int count=0; count<8; ++count)
std::cout << intStorage.get(count) << '\n';
// 为bool定义一个Storage8
Storage8<bool> boolStorage;
for (int count=0; count<8; ++count)
boolStorage.set(count, count & 3);
for (int count=0; count<8; ++count)
std::cout << (boolStorage.get(count) ? "true" : "false") << '\n';
return 0;
}
此示例打印:
0
1
2
3
4
五
6
7
假
真正
真正
真正
假
真正
真正
真正
虽然这个类完全正常,但事实证明,Storage8 的实现效率低于它需要的效率。由于所有变量都必须具有地址,并且CPU无法处理小于一个字节的任何内容,因此所有变量的大小必须至少为一个字节。因此,bool类型的变量最终使用整个字节,即使从技术上讲它只需要一个位来存储其真值或假值!因此,bool是1位有用信息和7位浪费空间。我们的Storage8 类包含8个bool,是1个字节的有用信息和7个字节的浪费空间。
事实证明,使用一些基本的位逻辑,可以将所有8个bool压缩成一个字节,从而完全消除了浪费的空间。但是,为了做到这一点,我们需要在类型bool使用时修改类,用一个大小为单字节的变量替换8个bool的数组。虽然我们可以创建一个全新的类来实现这一目标,但这有一个主要的缺点:我们必须给它一个不同的名称。然后程序员必须记住,Storage8 适用于非bool类型,而Storage8Bool(或我们称之为新类的任何内容)适用于bool。这是我们宁愿避免的不必要的复杂性。幸运的是,C ++为我们提供了一个更好的方法:类模板特化。
类模板专业化
类模板特化允许我们为特定数据类型(或数据类型,如果有多个模板参数)专门化模板类。在这种情况下,我们将使用类模板特化来编写一个定制版本的Storage8 ,它将优先于通用的Storage8 类。这类似于专用函数优先于通用模板函数的方式。
类模板特化被视为完全独立的类,即使它们的分配方式与模板化的类相同。这意味着我们可以改变我们的专业化类的任何内容,包括它的实现方式,甚至是公开的函数,就好像它是一个独立的类。这是我们的专业类:
template <> // 以下是没有模板化参数的模板类
class Storage8<bool> // 我们专注于Storage8 为bool
{
// 以下是标准类实现细节
private:
unsigned char m_data;
public:
Storage8() : m_data(0)
{
}
void set(int index, bool value)
{
// 找出我们正在设置/取消设置的位
// 这将使我们有兴趣打开/关闭的位数为1
unsigned char mask = 1 << index;
if (value) // 如果我们设置一下
m_data |= mask; // 按位使用或打开该位
else //如果我们转过身来
m_data &= ~mask; // 按位和反转掩码将该位关闭
}
bool get(int index)
{
// 弄清楚我们得到了什么
unsigned char mask = 1 << index;
//按位 - 并获取我们感兴趣的位的值
// 然后隐式转换为布尔型。
return (m_data & mask) != 0;
}
};
首先,请注意我们从一开始template<>。template关键字告诉编译器后面的内容是模板化的,空角度括号表示没有任何模板参数。在这种情况下,没有任何模板参数,因为我们用特定类型(bool)替换唯一的模板参数(typename T)。
接下来,我们添加类名称以表示我们专门使用类Storage8的bool版本。
所有其他更改都只是类实现细节。您不需要了解位逻辑如何工作以使用该类(尽管您可以查看按位运算符,如果您想要弄清楚,但需要复习按位运算符的工作方式)。
请注意,此特化类使用单个unsigned char(1个字节)而不是8个bool(8个字节)的数组。
现在,当我们声明一个类型为Storage8 的类时,其中T不是bool,我们将从通用模板化Storage8 类中获得一个版本。当我们声明一个类型为Storage8 的类时,我们将获得刚刚创建的专用版本。请注意,我们保持两个类的公开接口相同 - 而C ++让我们可以自由地添加,删除或更改Storage8 的函数,保持一致的接口意味着程序员可以使用以完全相同的方式上课。
我们可以使用与以前完全相同的示例来显示正在实例化的Storage8 和Storage8 :
int main()
{
// 为整数定义Storage8(实例化Storage8 <T>,其中T = int)
Storage8<int> intStorage;
for (int count=0; count<8; ++count)
intStorage.set(count, count);
for (int count=0; count<8; ++count)
std::cout << intStorage.get(count) << '\n';
// 为bool定义Storage8(实例化Storage8 <bool>专门化)
Storage8<bool> boolStorage;
for (int count=0; count<8; ++count)
boolStorage.set(count, count & 3);
for (int count=0; count<8; ++count)
std::cout << (boolStorage.get(count) ? "true" : "false") << '\n';
return 0;
}
正如您所料,这将打印与使用非专用版Storage8 的上一个示例相同的结果:
0
1
2
3
4
五
6
7
假
真正
真正
真正
假
真正
真正
真正
值得注意的是,保持模板类和所有特化项之间的公共接口通常是一个好主意,因为它使它们更容易使用 - 但是,它并不是绝对必要的。