C++基础教程面向对象(学习笔记(70))

类模板专业化

在函数模板专业化中,我们了解了如何专门化函数以便为特定数据类型提供不同的功能。事实证明,它不仅可以专门化功能,还可以专门化整个类!

考虑您想要设计一个存储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

真正
真正
真正

真正
真正
真正
值得注意的是,保持模板类和所有特化项之间的公共接口通常是一个好主意,因为它使它们更容易使用 - 但是,它并不是绝对必要的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值