Skip to content

Latest commit

 

History

History
220 lines (163 loc) · 10.6 KB

19.6 — Class template specialization.md

File metadata and controls

220 lines (163 loc) · 10.6 KB

19.6 — Class template specialization

In the previous lesson 19.5 -- Function template specialization, we saw how it was possible to specialize functions in order to provide different functionality for specific data types. As it turns out, it is not only possible to specialize functions, it is also possible to specialize an entire class!

在前面讲了函数模板特例化,自然类模板也是可以特例化的。

Consider the case where you want to design a class that stores 8 objects. Here’s a simplified class to do so:

假设你在设计一个存储八个对象的类

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];
    }
};

Because this class is templated, it will work fine for any given type:

因为这是个模板类,所以它可以对任何指定类型管用

#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;
}

This example prints:

0
1
2
3
4
5
6
7
false
true
true
true
false
true
true
true

While this class is completely functional, it turns out that the implementation of Storage8 is much more inefficient than it needs to be. Because all variables must have an address, and the CPU can’t address anything smaller than a byte, all variables must be at least a byte in size. Consequently, a variable of type bool ends up using an entire byte even though technically it only needs a single bit to store its true or false value! Thus, a bool is 1 bit of useful information and 7 bits of wasted space. Our Storage8 class, which contains 8 bools, is 1 byte worth of useful information and 7 bytes of wasted space.

虽然这个类完全管用,但是Storage8这个东西的效率不高。因为所有的变量都有一个地址。CPU也不能定位小于一个字节的东西,所有的变量都必须至少一个字节那么大。因此,一个bool类型的变量使用了一个字节那么大,而它只需要一个位就可以了。所以有7个位的空间就浪费掉了。我们的 Storage8存储了8个bool,但是用了64个字节,实际上一个字节就够了。

As it turns out, using some basic bit logic, it’s possible to compress all 8 bools into a single byte, eliminating the wasted space altogether. However, in order to do this, we’ll need to revamp the class when used with type bool, replacing the array of 8 bools with a variable that is a single byte in size. While we could create an entirely new class to do so, this has one major downside: we have to give it a different name. Then the programmer has to remember that Storage8 is meant for non-bool types, whereas Storage8Bool (or whatever we name the new class) is meant for bools. That’s needless complexity we’d rather avoid. Fortunately, C++ provides us a better method: class template specialization.

所以用一些位运算来把8个bool整合进一个字节是可能的,这样就不会浪费空间了。然而要这么干的话,我们就得让这个模板类在和bool类型搭配的时候,有特殊的行为。虽然我们可以创建一个完全新的类,但是这有一个主要的缺点,我们要给他一个不同的名字。程序员还得记住Storage8 不为bool服务,Storage8Bool才是给bool准备的。这正是我们试图避免的没必要的复杂度。还好C++给我们提供了更好的方法:类模板特例化。

Class template specialization

Class template specialization allows us to specialize a template class for a particular data type (or data types, if there are multiple template parameters). In this case, we’re going to use class template specialization to write a customized version of Storage8 that will take precedence over the generic Storage8 class. This works analogously to how a specialized function takes precedence over a generic template function.

类模板特例化允许我们对于特定的类型,特例化一个模板类。在这种情况下,我们用类模板特例化来写我们定制的Storage8类,这个类会优先于默认的泛型类被使用。这个事情和模板函数特例化优先于泛型模板函数是一样的。

Class template specializations are treated as completely independent classes, even though they are allocated in the same way as the templated class. This means that we can change anything and everything about our specialization class, including the way it’s implemented and even the functions it makes public, just as if it were an independent class. Here’s our specialized class:

类模板特例化被认为完全独立的类。(?)这意味我们可以改变我们特例化类的一切,包括实现方式和让一个函数变成public,就像是它是一个独立的类。(感觉像是只借用了一下模板的名字)。

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);
    }
};

First, note that we start off with template<>. The template keyword tells the compiler that what follows is templated, and the empty angle braces means that there aren’t any template parameters. In this case, there aren’t any template parameters because we’re replacing the only template parameter (T) with a specific type (bool).

首先,我们从template<>开始。template告诉编译器后面跟的是一个模板的东西,空的尖括号意味着里面没有模板参数。在这个案例中,没有参数是因为我们正在用特定的类型bool替换掉唯一的模板参数T。

Next, we add <bool> to the class name to denote that we’re specializing a bool version of class Storage8.

然后我们给类名增加了,来表示我们正在特例化一个bool版本的类Storage8.

All of the other changes are just class implementation details. You do not need to understand how the bit-logic works in order to use the class (though you can review O.2 -- Bitwise operators if you want to figure it out, but need a refresher on how bitwise operators work).

其他所有的改变就是类里面的函数实现了,你不需要理解位运算是怎么回事。

Note that this specialization class utilizes a single unsigned char (1 byte) instead of an array of 8 bools (8 bytes).

注意这个特化的版本没有用数组,而是用了一个无符号的char类型。

Now, when we declare a class of type Storage8, where T is not a bool, we’ll get a version stenciled from the generic templated Storage8 class. When we declare a class of type Storage8, we’ll get the specialized version we just created. Note that we have kept the publicly exposed interface of both classes the same -- while C++ gives us free reign to add, remove, or change functions of Storage8 as we see fit, keeping a consistent interface means the programmer can use either class in exactly the same manner.

当我们在声明一个类型Storage8的时候,当T不是bool的时候,我们就会从泛型的模板类中得到一份类型参数实例化的模板类实例。当我们声明一个Storage8 的类的时候,我们会得到我们自己写的特例化的版本。注意到我们保持了公开的接口,但是C++给了我们添加修改函数的自由,只要我们喜欢就好。保持一个一致性的接口意味着程序员可以以相同的行为来使用这些类。

We can use the exact same example as before to show both Storage8 and Storage8 being instantiated:

我们可以使用和之前相同的例子来展示我们的特例化类的用法和之前没有区别。

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;
}

As you might expect, this prints the same result as the previous example that used the non-specialized version of Storage8:

像你预料的一样,它的输出和之前的输出是一样的。

0
1
2
3
4
5
6
7
false
true
true
true
false
true
true
true

It’s worth noting again that keeping the public interface between your template class and all of the specializations identical is generally a good idea, as it makes them easier to use -- however, it’s not strictly necessary.

值得再次注意:让你特例化的类和那些泛型的类保持相同的公开接口非常有意义。这样能让写出来的类很好用,这也不是必须要这样的,你也可以不一样。