作者:jicanmeng
时间:2017年06月05日
使用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了,就直接使用这个了。
使用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了,就直接使用这个了。
上面说的是多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; }
模板部分的内容到此为止,后面的就是多多练习了。