19.6 类模板特化¶
By Alex on August 16th, 2008 | last modified by Alex on December 21st, 2020
翻译 by dashjay 2020-12-27 |最后修改于 2020-12-27
在之前的课程 19.5 -- Function template specialization,我们知道了可以特化函数,为了给特殊的数据类型提供不同的功能。不仅仅可以特化函数,也可以特化整个类!
思考这个例子,你想设计一个类可以存储 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) const
{
return m_array[index];
}
};
因为这个类是模板类,它能在任何给定的数据类型下工作:
#include <iostream>
int main()
{
// Define a Storage8 for integers
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';
// Define a Storage8 for bool
Storage8<bool> boolStorage;
for (int count{ 0 }; count < 8; ++count)
boolStorage.set(count, count & 3);
std::cout << std::boolalpha;
for (int count{ 0 }; count<8; ++count)
{
std::cout << boolStorage.get(count) << '\n';
}
return 0;
}
例子打印:
0
1
2
3
4
5
6
7
false
true
true
true
false
true
true
true
这个类是可以完全正常工作的,但是就是 Storage8<bool>
的实现效率远远低于实际需要。因为所有的变量必须有一个地址,并且 CPU 不能定位比一 byte 更小的单位,所有变量必须至少是 1 byte。因此,一个 bool 值整整用掉了整个 byte,然后 7 bits 都被浪费了。我们的 Storage8<bool>
类一共包含了 8 个bool值,其中 1 byte 是包含有用的信息,另外 7 byte 都是浪费的。
结果,使用一个基础位逻辑,有可能吧 8 个 bool 值压缩到一个单 byte 中,消除浪费的空间到一起。然而,为了达成这个目的,我们需要在使用 bool 的时候,修改这个类型,替换 8 个 bool 的数组为一个单 byte 大小的变量。我们当然可以创建整个的新类来完成需求,但是这样做有一个最大的缺点就是:我们不得不取一个新的名字。程序员就必须记在脑子里, Storage8<T>
不能提供给 bool 类使用,必须使用 Storage8Bool
(或者什么其他的新类)。这些没有必要的复杂我们应该避免。幸运的是,C++ 给我们提供了一种更好的方法:类模板特化。
类模板特化¶
类模板特化允许我们特化模板在某个特定类型(或者几个不同的数据类型,如果不止一个模板参数的话)。在这个情况下,我们要去使用模板类特化来写一个特定版本的 Storage8<bool>
将会比泛型的 Storage8<T>
类优先级更高。这个类似于特定函数优先于泛型模板函数。
尽管他们和模板类一样,以同样的方式分配,类模板特化被当做是独立的类。这意味着我们可以改变关于我们特化模板的任何内容,包含它实现·的方式,甚至是公开的函数,就像他是一个独立的类,这是我们的特化类:
template <> // the following is a template class with no templated parameters
class Storage8<bool> // we're specializing Storage8 for bool
{
// What follows is just standard class implementation details
private:
unsigned char m_data{};
public:
void set(int index, bool value)
{
// Figure out which bit we're setting/unsetting
// This will put a 1 in the bit we're interested in turning on/off
auto mask{ 1 << index };
if (value) // If we're setting a bit
m_data |= mask; // Use bitwise-or to turn that bit on
else // if we're turning a bit off
m_data &= ~mask; // bitwise-and the inverse mask to turn that bit off
}
bool get(int index)
{
// Figure out which bit we're getting
auto mask{ 1 << index };
// bitwise-and to get the value of the bit we're interested in
// Then implicit cast to boolean
return (m_data & mask);
}
};
首先,注意我们使用 template<>
来开始。模板关键词告诉编译器下方的内容是属于模板的,空的尖括号表示没有任何模板参数。在这种情况下,无需任何的模板参数,因为我们替换仅有的模板参数(T)为特定的类型(bool)。
下一步,我们添加了 <bool>
到类名来表示我们特化了 bool 版本的 Storage8
类。
所有的其他的改变就只是类实现的细节。仅仅为了使用这个类,你完全没有必要理解位逻辑(尽管你可以复习 O.2 -- Bitwise operators 如果你想要解决,但是需要复习如何进行未操作)。
注意这个特化类利用了一个 single unsigned char (1 byte) 替换了一个 8 个 bool 的数组(8 bytes)。
现在,当我们生命一个类型为 Storage8<T>
,并且 T 不是一个 bool 类型,我们就会获得一个从通用模板类 Storage8<T>
生成的版本。
现在,当我们声明了一个 Storage8<T>
的类,当 T
不是一个 bool
,我们将会得到一个从通用泛型版本模板 Storage8<T>
的版本。当我们定义一个 Stroage8<bool>
的对象,我们会得到刚刚创建的特化的版本。注意,我们保持了两个类的公开接口相同 ———— C++ 给我们自由来添加,移除,或者将 Storage8<bool>
的函数改成我们看到的那样,来适配 T
所制定的类型,保持了一个统一的接口是的程序员可以以同样的方式来使用这个类。
我们可以使用和之前恰好相同的方式来展示 Storage8<T>
和 Storage8<bool>
被实例化:
int main()
{
// Define a Storage8 for integers (instantiates Storage8<T>, where 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';
}
// Define a Storage8 for bool (instantiates Storage8<bool> specialization)
Storage8<bool> boolStorage;
for (int count{ 0 }; count < 8; ++count)
{
boolStorage.set(count, count & 3);
}
std::cout << std::boolalpha;
for (int count{ 0 }; count < 8; ++count)
{
std::cout << boolStorage.get(count) << '\n';
}
return 0;
}
如你所期待的那样,这打印出了和之前没有进行模板特化的时候的 Storage8<bool>
产生相同的结果。
0
1
2
3
4
5
6
7
false
true
true
true
false
true
true
true
值得再次注意的是,保持模板类和模板特化的公开接口相同是一个不错的习惯,因为它让这些代码更易用 ———— 然而,也不是严格有必要这样做的。