分类: C/C++
2008-03-18 16:19:52
介绍
如果你是个模板的高手,你就可以将ATL的学习作为一种享受。在这一节中,我将要尝试解释一些ATL使用的模板技术。我不能保证你读完本节后能成为一个模板高手,只能是尽我所能让你在读完本文后能够更轻松地理解ATL的源码。
程序35.
#include程序的输出为:using namespace std; template T Maximum(const T& a, const T& b) { return a > b ? a : b; } int main() { cout << Maximum(5, 10) << endl; cout << Maximum(''A'', ''B'') << endl; return 0; }
10 B在这里,由于模板函数的关系,我们就没有必要分别重载int和char数据类型的函数版本了。其中很重要的一点是,函数的两个参数类型必须一致。但是如果我们传入了不同的数据类型,我们就需要告知编译器应该把这个参数考虑为哪种数据类型。
#include程序的输出为:using namespace std; template T Maximum(const T& a, const T& b) { return a > b ? a : b; } int main() { cout << Maximum (5, ''B'') << endl; cout << Maximum (5, ''B'') << endl; return 0; }
66 B我们也可以编写类模板,下面就是一个简单版本的堆栈类模板。
#include这个程序中没有任何错误检验,不过这个程序的目的只是示范模板的用法,而不是真的要写一个专业的堆栈类。using namespace std; template class Stack { private: T* m_pData; int m_iTop; public: Stack(int p_iSize = 0) : m_iTop(0) { m_pData = new T[p_iSize]; } void Push(T p_iData) { m_pData[m_iTop++] = p_iData; } T Pop() { return m_pData[--m_iTop]; } T Top() { return m_pData[m_iTop]; } ~Stack() { if (m_pData) { delete [] m_pData; } } private: Stack(const Stack &); Stack & operator = (const Stack &); }; int main() { Stack a(10); a.Push(10); a.Push(20); a.Push(30); cout << a.Pop() << endl; cout << a.Pop() << endl; cout << a.Pop() << endl; return 0; }
30 20 10我们也可以将数据类型作为一个模板参数来传递,并且为它设置一个默认值。让我们来稍微修改一下程序37(译注:原文为“程序36”,应为37),并将堆栈的尺寸作为一个模板参数来传递,而不是作为构造函数的参数。
#include程序的输出和前一个相同。这个程序最重要的一点为:using namespace std; template class Stack { private: T m_pData[iSize]; int m_iTop; public: Stack() : m_iTop(0) { } void Push(T p_iData) { m_pData[m_iTop++] = p_iData; } T Pop() { return m_pData[--m_iTop]; } T Top() { return m_pData[m_iTop]; } private: Stack(const Stack &); Stack & operator = (const Stack &); }; int main() { Stack a; a.Push(10); a.Push(20); a.Push(30); cout << a.Pop() << endl; cout << a.Pop() << endl; cout << a.Pop() << endl; return 0; }
template现在就有一个问题:哪一个更好呢?通常,传递模板参数的办法是优于给构造函数传递参数的。为什么呢?因为在你将堆栈尺寸作为模板参数传递的时候,这个给定数据类型的数组就会被自动创建;而给构造函数传递参数则意味着构造函数会在运行时使用new或malloc一系列功能来分配内存。如果我们已经确定在创建好堆栈之后就不再更改它的尺寸(就像上面程序中private段中拷贝构造函数和赋值运算符中的那样)了,那么无疑使用模板参数是更加适合的。
#include程序的输出为:using namespace std; template T Maximum(const T& a, const T& b) { return a > b ? a : b; } class Point { private: int m_x, m_y; public: Point(int p_x = 0, int p_y = 0) : m_x(p_x), m_y(p_y) { } bool friend operator > (const Point& lhs, const Point& rhs) { return lhs.m_x > rhs.m_x && lhs.m_y > rhs.m_y; } friend ostream& operator << (ostream& os, const Point& p) { return os << "(" << p.m_x << ", " << p.m_y << ")"; } }; int main() { Point a(5, 10), b(15, 20); cout << Maximum(a, b) << endl; return 0; }
(15, 20)同样,我们也能够将一个类模板作为一个模板参数传递。现在让我们来编写这样一个Point类,并将其作为一个模板参数传递给Stack类模板。
#include程序的输出为:using namespace std; template class Point { private: T m_x, m_y; public: Point(T p_x = 0, T p_y = 0) : m_x(p_x), m_y(p_y) { } bool friend operator > (const Point & lhs, const Point & rhs) { return lhs.m_x > rhs.m_x && lhs.m_y > rhs.m_y; } friend ostream& operator << (ostream& os, const Point & p) { return os << "(" << p.m_x << ", " << p.m_y << ")"; } }; template class Stack { private: T m_pData[iSize]; int m_iTop; public: Stack() : m_iTop(0) { } void Push(T p_iData) { m_pData[m_iTop++] = p_iData; } T Pop() { return m_pData[--m_iTop]; } T Top() { return m_pData[m_iTop]; } private: Stack(const Stack &); Stack & operator = (const Stack &); }; int main() { Stack > st; st.Push(Point (5, 10)); st.Push(Point (15, 20)); cout << st.Pop() << endl; cout << st.Pop() << endl; return 0; }
(15, 20) (5, 10)这个程序中最重要的部分为:
Stack在这里,你必须在两个大于号之间放置一个空格,否则编译器就会将它看作>>(右移运算符)并产生错误。> st;
template换为
template现在我们就没有必要一定在创建Stack类对象的时候传递数据类型了,但是你仍然需要书写这一对尖括弧以告知编译器使用默认的数据类型。你可以这么创建对象:
Stack<> st;当你在类的外部定义(译注:原文此处是“declare”,我以为应该是“define”更准确一些。)类模板的成员函数的时候,你仍然需要写出带有模板参数的类模板全称。
#include程序的输出为:using namespace std; template class Point { private: T m_x, m_y; public: Point(T p_x = 0, T p_y = 0); void Setxy(T p_x, T p_y); T getX() const; T getY() const; friend ostream& operator << (ostream& os, const Point & p) { return os << "(" << p.m_x << ", " << p.m_y << ")"; } }; template Point ::Point(T p_x, T p_y) : m_x(p_x), m_y(p_y) { } template void Point ::Setxy(T p_x, T p_y) { m_x = p_x; m_y = p_y; } template T Point ::getX() const { return m_x; } template T Point ::getY() const { return m_y; } int main() { Point p; p.Setxy(20, 30); cout << p << endl; return 0; }
(20, 30)让我们来稍微修改一下程序35,传递字符串值(而不是int或float)作为参数,并看看结果吧。
#include程序的输出为Karachi。(译注:在我的Visual Studio.net 2003下的输出却为Pakistan,这不同的原因是编译器组织字符串地址的方式不同决定的,但是Maximum函数的结果是应该返回内存高位的那个地址的,这和作者说的道理是一致的。)为什么呢?因为这里char*作为模板参数传递, Karachi在内存中存储的位置更高,而>运算符仅仅比较这两个地址值而不是字符串本身。using namespace std; template T Maximum(T a, T b) { return a > b ? a : b; } int main() { cout << Maximum("Pakistan", "Karachi") << endl; return 0; }
#include至于类模板,也可以用相同的办法进行特化。using namespace std; template T Maximum(T a, T b) { return a > b ? a : b; } template <> char* Maximum(char* a, char* b) { return strlen(a) > strlen(b) ? a : b; } int main() { cout << Maximum("Pakistan", "Karachi") << endl; return 0; }
#include程序的输出为:using namespace std; template class TestClass { public: void F(T pT) { cout << "T version" << ''\t''; cout << pT << endl; } }; template <> class TestClass { public: void F(int pT) { cout << "int version" << ''\t''; cout << pT << endl; } }; int main() { TestClass obj1; TestClass obj2; obj1.F(''A''); obj2.F(10); return 0; }
T version A int version 10ATL中就有若干类是类似这样的特化版本,例如在ATLBASE.H中定义的CComQIPtr。
#include在这里,Round1和Round2为一个游戏中不同的关卡类,并且Strategy类依靠传递的模板参数来决定该做些什么。using namespace std; class Round1 { public: void Play() { cout << "Round1::Play" << endl; } }; class Round2 { public: void Play() { cout << "Round2::Play" << endl; } }; template class Strategy { private: T objT; public: void Play() { objT.Play(); } }; int main() { Strategy obj1; Strategy obj2; obj1.Play(); obj2.Play(); return 0; }
Round1::Play Round2::PlayATL就是使用Strategy设计模式来实现线程的。
#include程序的输出为:using namespace std; class Inner { public: void Fun() { cout << "Inner::Fun" << endl; } }; class Outer { private: Inner* m_pInner; public: Outer(Inner* p_pInner) : m_pInner(p_pInner) { } Inner* operator -> () { return m_pInner; } }; int main() { Inner objInner; Outer objOuter(&objInner); objOuter->Fun(); return 0; }
Inner::Fun()简单地说来,我们仅仅重载了->运算符,但是在实际的智能指针中,所有必须的运算符(例如=、==、!、&、*)都需要被重载。以上的智能指针有一个大问题:它只能包含指向Inner对象的指针。我们可以编写Outer类模板来取消这一限制,现在让我们来略微修改一下程序。
#include程序的输出和前一个一样,但是现在Outer类就可以包含任何类型了,只需要把类型作为模板参数传递进来即可。using namespace std; class Inner { public: void Fun() { cout << "Inner::Fun" << endl; } }; template class Outer { private: T* m_pInner; public: Outer(T* p_pInner) : m_pInner(p_pInner) { } T* operator -> () { return m_pInner; } }; int main() { Inner objInner; Outer objOuter(&objInner); objOuter->Fun(); return 0; }
#include程序的输出为:using namespace std; class Base1 { public: Base1() { cout << "Base1::Base1" << endl; } }; class Base2 { public: Base2() { cout << "Base2::Base2" << endl; } }; template class Drive : public T { public: Drive() { cout << "Drive::Drive" << endl; } }; int main() { Drive obj1; Drive obj2; return 0; }
Base1::Base1 Drive::Drive Base2::Base2 Drive::Drive在这里,Drive类是继承自Base1还是Base2是由在对象创建的时候传递给模板的参数决定的。
#include程序的输出为:using namespace std; class Base { public: virtual void fun() { cout << "Base::fun" << endl; } void doSomething() { fun(); } }; class Drive : public Base { public: void fun() { cout << "Drive::fun" << endl; } }; int main() { Drive obj; obj.doSomething(); return 0; }
Drive::fun在模板的帮助下,我们可以实现与之相同的行为。
#include程序的输出和前一个是一样的,所以我们可以用模板来模拟虚函数的行为。using namespace std; template class Base { public: void fun() { cout << "Base::fun" << endl; } void doSomething() { T* pT = static_cast (this); pT->fun(); } }; class Drive : public Base { public: void fun() { cout << "Drive::fun" << endl; } }; int main() { Drive obj; obj.doSomething(); return 0; }
class Drive : public Base这表明我们可以将Drive类作为一个模板参数来传递。程序中另外一个有趣的地方是基类中的doSomething函数。{
T* pT = static_cast在这里基类的指针被转换为派生类的指针,因为派生类是作为Base类的模板参数传递的。这个函数可以通过指针来执行,由于指针指向了派生类的对象,所以派生类的对象就被调用了。(this); pT->fun();
#include程序的输出和前一个一样。但是对于虚函数的情况来说,输出就应该是:using namespace std; template class Base { public: void fun() { cout << "Base::fun" << endl; } void doSomething() { T* pT = static_cast (this); pT->fun(); } }; class Drive : public Base { public: void fun() { cout << "Drive::fun" << endl; } }; class MostDrive : public Drive { public: void fun() { cout << "MostDrive::fun" << endl; } }; int main() { MostDrive obj; obj.doSomething(); return 0; }
MostDrive::fun这一技术还有另外一个问题,就是当我们使用Base类的指针来存储派生类的地址的时候。
#include这个程序会给出一个错误,因为我们没有向基类传递模板参数。现在我们稍微修改一下,并传递模板参数。using namespace std; template class Base { public: void fun() { cout << "Base::fun" << endl; } void doSomething() { T* pT = static_cast (this); pT->fun(); } }; class Drive : public Base { public: void fun() { cout << "Drive::fun" << endl; } }; int main() { Base* pBase = NULL; pBase = new Drive; return 0; }
#include现在程序正常工作,并给出了我们所期望的输出,也就是:using namespace std; template class Base { public: void fun() { cout << "Base::fun" << endl; } void doSomething() { T* pT = static_cast (this); pT->fun(); } }; class Drive : public Base { public: void fun() { cout << "Drive::fun" << endl; } }; int main() { Base * pBase = NULL; pBase = new Drive; pBase->doSomething(); return 0; }
Drive::fun但是在Base类有多个继承的时候,就会出现问题。为了更好地弄懂这一点,请看下面的程序。
#include程序会在下面的代码处给出错误:using namespace std; template class Base { public: void fun() { cout << "Base::fun" << endl; } void doSomething() { T* pT = static_cast (this); pT->fun(); } }; class Drive1 : public Base { public: void fun() { cout << "Drive1::fun" << endl; } }; class Drive2 : public Base { public: void fun() { cout << "Drive2::fun" << endl; } }; int main() { Base * pBase = NULL; pBase = new Drive1; pBase->doSomething(); delete pBase; pBase = new Drive2; pBase->doSomething(); return 0; }
pBase = new Drive2;因为pBase是一个指向Base