tempalte specialization(模板特例化)

作者:jicanmeng

时间:2017年06月05日


  1. function template specialization
  2. class template specialization
  3. class template内部的function的 specialization

1. function template specialization

使用function tempalate后,对于创建的每一个function template instance,都会执行相同的代码。如果我们希望某一个function template instance具有不同的行为,可以单独对这个function template instance进行修改。我们通过修改前面的例子来描述一下:

#include <iostream>

using namespace std;

template <typename T>  // this is the template parameter declaration
const T& max(const T& x, const T& y)
{
    return (x > y) ? x : y;
}

template <>
const double& max(const double& x, const double& y)
{
	cout << "compare double values" << endl;
	return 1.2345;
}

int main()
{
    int i = max(3, 7);
    std::cout << i << '\n';

    double d = max(6.34, 18.523);
    std::cout << d << '\n';

    char ch = max('a', '6');
    std::cout << ch << '\n';

    return 0;
}

以下是运行结果:

[jicanmeng@andy tmp]$ ./a.out
				7
				compare double values
				1.2345
				a
			[jicanmeng@andy tmp]$

当编译器遇到23行的max(6.34, 18.523)时,在根据max()模板创建一个function template instance时,发现代码中已经定义了一个function template instance了,就直接使用这个了。

2. class template specialization

使用class tempalate后,对于创建的每一个class template instance,都会执行相同的代码。如果我们希望某一个class template instance具有不同的行为或属性,可以单独对这个class template instance进行修改。举例如下:

#include <iostream>

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

template <>
class Storage8<double>
{
private:
    double m_array[2];

public:
    void setValue(int index, const double &value) {
        m_array[index] = value;
    }

    const double& getValue(int index) {
        return 3.3333;
    }
};

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

    for (int count = 0; count < 8; ++count)
        std::cout << (boolStorage.get(count) ? "true" : "false") << '\n';

    // Define a Storage8 for double
    Storage8<double> doubleStorage;
    for (int count = 0; count < 2; ++count)
        doubleStorage.setValue(count, 1.2345);

    for (int count = 0; count < 2; ++count)
       std::cout << doubleStorage.getValue(count) << '\n';

    return 0;
}

以下是运行结果:

[jicanmeng@andy tmp]$ ./a.out
                0
                1
                2
                3
                4
                5
                6
                7
                false
                true
                true
                true
                false
                true
                true
                true
                3.3333
                3.3333
            [jicanmeng@andy tmp]$

可以看出,我们对数据类型为double的storage类进行了特例化。当编译器在代码中55行遇到Storage8<double> doubleStorage;时,会发现代码中已经定义了一个class template instance了,就直接使用这个了。

3. class template内部的function的 specialization

上面说的是多class template中的某个class进行特例化,这个class中属性和方法我们可以任意定义。我们也可以只特例化这个class内部的某个方法。如下面例子所示:

#include <iostream>

template <class T>
class Storage
{
private:
    T m_value;
public:
    Storage(T value) {
        m_value = value;
    }

    ~Storage() {
    }

    void print() {
        std::cout << m_value << '\n';
    }
};

template <>
void Storage<double>::print() {
    std::cout << std::scientific << m_value << '\n';
}

int main()
{
    // Define some storage units
    Storage<int> nValue(5);
    Storage<double> dValue(6.7);

    // Print out some values
    nValue.print();
    dValue.print();

    return 0;
}

运行结果如下:

[jicanmeng@andy tmp]$ ./a.out
                5
                6.700000e+000
            [jicanmeng@andy tmp]$

在上面的例子中,只将类型为double的class中的print函数进行了特例化。learncpp.com上面是这样描述的:

When the compiler goes to instantiate Storage<double>::print(), it will see we’ve already explicitly defined that function, and it will use the one we’ve defined instead of stenciling out a version from the generic templated class.

The template <> tells the compiler that this is a template function, but that there are no template parameters (since in this case, we’re explicitly specifying all of the types). Some compilers may allow you to omit this, but it’s proper to include it.

但是上面的例子其实还是有bug的,如果我们给Storage模板传递一个char *类型参数,那么就会报错。比如:

#include <iostream>

template <class T>
class Storage
{
private:
    T m_value;
public:
    Storage(T value) {
        m_value = value;
    }

    ~Storage() {
    }

    void print() {
        std::cout << m_value << '\n';
    }
};

template <>
void Storage<double>::print() {
    std::cout << std::scientific << m_value << '\n';
}

int main()
{
    // Dynamically allocate a temporary string
    char *string = new char[40];

    // Ask user for their name
    std::cout << "Enter your name: ";
    std::cin >> string;

    // Store the name
    Storage<char*> value(string);

    // Delete the temporary string
    delete[] string;

    // if we do not make a Storage<char *> instance, this will print garbage
    value.print();

    return 0;
}

在visual studio 2013下运行结果如下:

[jicanmeng@andy tmp]$ ./a.out
                Enter your name: jicanmeng
                葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺
                请按任意键继续. . .
            [jicanmeng@andy tmp]$

原因我不说您也能明白,释放了string指向的内容,再次访问这个地址指向的内容就会出错。为了解决这个问题,我们需要指定当传入char *类型时,Storage模板应该如何实现。如下面代码:

#include <iostream>

template <class T>
class Storage
{
private:
    T m_value;
public:
    Storage(T value) {
        m_value = value;
    }

    ~Storage() {
    }

    void print() {
        std::cout << m_value << '\n';
    }
};

template <>
void Storage<double>::print() {
    std::cout << std::scientific << m_value << '\n';
}

template <>
Storage<char *>::Storage(char *value) {
    int length = strlen(value) + 1;
    m_value = new char[length];

    // Copy the actual value string into the m_value memory we just allocated
    for (int count = 0; count < length; ++count) {
        m_value[count] = value[count];
    }
}
template <>
Storage<char*>::~Storage()
{
    if (m_value) {
        delete[] m_value;
    }
}

int main()
{
    // Dynamically allocate a temporary string
    char *string = new char[40];

    // Ask user for their name
    std::cout << "Enter your name: ";
    std::cin >> string;

    // Store the name
    Storage<char*> value(string);

    // Delete the temporary string
    delete[] string;

    // if we do not make a Storage<char *> instance, this will print garbage
    value.print();

    return 0;
}

在visual studio 2013下运行结果如下:

[jicanmeng@andy tmp]$ ./a.out
                Enter your name: jicanmeng
                jicanmeng
                请按任意键继续. . .
            [jicanmeng@andy tmp]$

看起来是没有问题了,但其实代码还是存在bug的。我们可以传递char *类型了,但是后面如果想要传递int *类型或者double *类型呢?一个个定义显然是太麻烦了。我们可以通过部分特例化(Partial template specialization)来解决这个问题。我们可以将指针类型统一进行处理,如下面代码所示:

#include <iostream>

template <class T>
class Storage
{
private:
    T m_value;
public:
    Storage(T value) {
        m_value = value;
    }

    ~Storage() {
    }

    void print() {
        std::cout << m_value << '\n';
    }
};

template <>
void Storage<double>::print() {
    std::cout << std::scientific << m_value << '\n';
}

template <class T>
class Storage<T*> // this is a partial-specialization of Storage that works with pointer types
{
private:
    T* m_value;
public:
    Storage(T* value) {
        // For pointers, we'll do a deep copy
        m_value = new T(*value);
    }

    ~Storage() {
        delete m_value; // so we use scalar delete here, not array delete
    }

    void print() {
        std::cout < *m_value < '\n';
    }
};

int main()
{
    int *pa = new int(3);
    Storage<int *> valueOne(pa);

    delete pa;

    valueOne.print();

    return 0;
}

如果你足够细心,你会发现,哎呀,此时的代码传入char *类型又不好使了。怎么办呢?好办。在上面代码的基础上面再特别加上对char *类型的处理就行了。所以,最终的代码如下面所示:

#include <iostream>

template <class T>
class Storage
{
private:
    T m_value;
public:
    Storage(T value) {
        m_value = value;
    }

    ~Storage() {
    }

    void print() {
        std::cout << m_value << '\n';
    }
};

template <>
void Storage<double>::print() {
    std::cout << std::scientific << m_value << '\n';
}

template <>
Storage<char *>::Storage(char *value) {
    int length = strlen(value) + 1;
    m_value = new char[length];

    // Copy the actual value string into the m_value memory we just allocated
    for (int count = 0; count < length; ++count) {
        m_value[count] = value[count];
    }
}
template <>
Storage<char*>::~Storage()
{
    if (m_value) {
        delete[] m_value;
    }
}
// Full specialization of print function for type char*
// Without this, printing a Storage<char*> would call Storage<T*>::print(), which only prints the first element
template <>
void Storage<char*>::print()
{
    std::cout << m_value;
}

template <class T>
class Storage<T*> // this is a partial-specialization of Storage that works with pointer types
{
private:
    T* m_value;
public:
    Storage(T* value) {
        // For pointers, we'll do a deep copy
        m_value = new T(*value);
    }

    ~Storage() {
        delete m_value; // so we use scalar delete here, not array delete
    }

    void print() {
        std::cout < *m_value < '\n';
    }
};

int main()
{
    // 1. int * type
    int *pa = new int(3);
    Storage<int *> valueOne(pa);

    delete pa;

    valueOne.print();

    // 2. char * type
    // Dynamically allocate a temporary string
    char *string = new char[40];

    // Ask user for their name
    std::cout << "Enter your name: ";
    std::cin >> string;

    // Store the name
    Storage<char*> value(string);

    // Delete the temporary string
    delete[] string;

    // if we do not make a Storage<char *> instance, this will print garbage
    value.print();
    return 0;
}

模板部分的内容到此为止,后面的就是多多练习了。

参考资料

  1. <<C++实用教程>> 电子工业出版社 郑阿奇 主编 丁有和 编著
  2. The C++ Tutorial:
    http://www.learncpp.com/cpp-tutorial/13-5-function-template-specialization/
    http://www.learncpp.com/cpp-tutorial/136-class-template-specialization/