剖析C++模板(下)

类别:VC语言 点击:0 评论:0 推荐:
虚模板成员函数在模板类中的嵌套是不允许的 模板的特殊化 全局的特殊化 局部特殊化 一个特殊化的例子

你可以像使用普通类的方法来使用模板类,这一点是毫无疑问的,例如:你可以继承、可以创建一个从现有模板继承过来的并已经初始化的模板。如果vector已经为你做了所有的事,但你还不满足,想加入sort的功能,你可以非常简单的用下面的代码来扩充它。

//: C03:Sorted.h

// Template specialization

#ifndef SORTED_H

#define SORTED_H

#include <vector>

template<class T>

class Sorted : public std::vector<T>

{

public:

void sort();

};

 

template<class T>

void Sorted<T>::sort()

{

// A bubble sort

for(int i = size(); i > 0; i--)

for(int j = 1; j < i; j++)

if(at(j-1) > at(j))

{

// Swap the two elements:

T t = at(j-1);

at(j-1) = at(j);

at(j) = t;

}

}

 

// Partial specialization for pointers:

template<class T>

class Sorted<T*> : public std::vector<T*>

{

public:

void sort();

};

 

template<class T>

void Sorted<T*>::sort()

{

for(int i = size(); i > 0; i--)

for(int j = 1; j < i; j++)

if(*at(j-1) > *at(j))

{

// Swap the two elements:

T* t = at(j-1);

at(j-1) = at(j);

at(j) = t;

}

}

 

// Full specialization for char*:

template<>

void Sorted<char*>::sort()

{

for(int i = size(); i > 0; i--)

for(int j = 1; j < i; j++)

if(strcmp(at(j-1), at(j)) > 0)

{

// Swap the two elements:

char* t = at(j-1);

at(j-1) = at(j);

at(j) = t;

}

}

#endif // SORTED_H ///:~

这个排序的模板对所有使用它的类强制了一个限制:他们必须包含一个“>”操作符,在sstring中,已经明确添加了这个特性,但在integer类中,自动类型转换符int提供了一个构造时(built-in)> 操作。当一个模板愿意为你提供更多的函数时,交易的代价往往是对你的类提出更多的要求,所以请注意在这里重载操作符的代价,Integer类可能必须要特殊的代码来实现这些功能。

缺省的排序模板只能和对象协同工作(包括构造类型的对象)。然而,它不会对指向对象的指针排序,所以局部特殊化是必要的。甚至局部特殊化的代码也不能对char *类型的数组排序,这是我们就不得不需要全局特殊的比较char*元素,使用strcmp()来做合适的操作。

这里有一个例子使用了起初我们讲到的随机数发生器。

//: C03:Sorted.cpp

// Testing template specialization

#include "Sorted.h"

#include "Urand.h"

#include "../arraySize.h"

#include <iostream>

#include <string>

using namespace std;

char* words[] = {

"is", "running", "big", "dog", "a",

};

char* words2[] = {

"this", "that", "theother",

};

int main()

{

Sorted<int> is;

Urand<47> rand;

for(int i = 0; i < 15; i++)

is.push_back(rand());

for(int l = 0; l < is.size(); l++)

cout << is[l] << ' ';

cout << endl;

is.sort();

for(int l = 0; l < is.size(); l++)

cout << is[l] << ' ';

cout << endl;

// Uses the template partial specialization:

Sorted<string*> ss;

for(int i = 0; i < asz(words); i++)

ss.push_back(new string(words[i]));

for(int i = 0; i < ss.size(); i++)

cout << *ss[i] << ' ';

cout << endl;

ss.sort();

for(int i = 0; i < ss.size(); i++)

cout << *ss[i] << ' ';

cout << endl;

// Uses the full char* specialization:

Sorted<char*> scp;

for(int i = 0; i < asz(words2); i++)

scp.push_back(words2[i]);

for(int i = 0; i < scp.size(); i++)

cout << scp[i] << ' ';

cout << endl;

scp.sort();

for(int i = 0; i < scp.size(); i++)

cout << scp[i] << ' ';

cout << endl;

} ///:~

这里的每一个模板实例都使用了不同版本的模板。Sorted<int>使用了“ordinary”,未特殊指明的模板,Sorted<char*>使用了全局特殊化。提示你一句:如果没有全局特殊化,你可能会傻傻的在那里想这样一个问题:程序看上去工得很正常,因为words数组被整齐的排序为;“a big dog is running”,但同样是使用局部特殊化,words2却不能正常工作,为了我们渴望追求的结果,全局特殊化是必要的。

 

指针特殊化 模板函数的局部顺序 设计和效率

在Sorted中,每次你调用add(),元素被插入数组,然后数组被排序。这里,可怕的低效和倍受批评的冒泡排序算法被使用了。但这是完全可以赞同的,因为它是私有实现部分的。在程序发展过程中,你的优先级是:

1.  得到正确的类的接口。

2.  尽可能精确的实现,当然也要尽可能的快。

3.  验证你的设计

通常,你只有在整理你的最原始的“初稿”时,才能发现类中接口存在的问题。你也可能在组装和检验你的最初的实现代码时发现需要一些可以作为好帮手的类,例如容器、iterators等。有些时候,仅仅是在分析过程中,很难发现这样的问题。在分析过程中,你的目标只是有一个宏伟的蓝图和快速实现以及测试。只有在设计阶段之后,才能意识到你应该花一些时间重新彻底的考虑它并细心考虑上面的话题。

 

防止模板膨胀

每次你实例化一个模板,模板的代码都会被重新生成(除了inline标记的函数),如果一个模板某些函数不依赖于特定的类型参数而存在,那他们就可以放置在一个通用的基础类中,来阻止无意义的代码重生。例如:在前面的章节中InheritStack.cpp规定,只有Stack类型才能接受过程调用,下面是模板化的代码版本。

//: C03:Nobloat.h

// Templatized InheritStack.cpp

#ifndef NOBLOAT_H

#define NOBLOAT_H

#include "../C0A/Stack4.h"

template<class T>

class NBStack : public Stack

{

public:

void push(T* str)

{

Stack::push(str);

}

T* peek() const

{

return (T*)Stack::peek();

}

T* pop()

{

return (T*)Stack::pop();

}

~NBStack();

};

 

// Defaults to heap objects & ownership:

template<class T>

NBStack<T>::~NBStack()

{

T* top = pop();

while(top)

{

delete top;

top = pop();

}

}

#endif // NOBLOAT_H ///:~

 

想前面提到的,inline函数因不产生新的代码所以他们是自由的,在整个过程中,功能性的代码只是在我们创建基础类代码时产生了一次,而且,所属权的问题也因为增加了新的析构函数(类型无关,由模板创建)而解决了。注意下面的代码,当基础类的析构函数被调用的时候,堆栈被清空了,于是就不会有重复释放的问题了。

//: C03:NobloatTest.cpp

#include "Nobloat.h"

#include "../require.h"

#include <fstream>

#include <iostream>

#include <string>

using namespace std;

int main(int argc, char* argv[])

{

requireArgs(argc, 1); // File name is argument

ifstream in(argv[1]);

assure(in, argv[1]);

NBStack<string> textlines;

string line;

// Read file and store lines in the stack:

while(getline(in, line))

textlines.push(new string(line));

// Pop the lines from the stack and print them:

string* s;

while((s = (string*)textlines.pop()) != 0)

{

cout << *s << endl;

delete s;

}

} ///:~

 

外部实例化

有些时候,外部实例化一个模板是非常有用的。那就是说:告所编译器先放下代码等待一个特殊版本的模板即使当时你还没有打算创建一个对象,为了做到这一点,你可以按照下面的方法使用template关键字。

template class Bobbin<thread>;

template void sort<char>(char*[]);

下面就是Sorted.cpp的例子,它在使用之前就已经外部实例化了一个模板。

//: C03:ExplicitInstantiation.cpp

#include "Urand.h"

#include "Sorted.h"

#include <iostream>

using namespace std;

// Explicit instantiation:

template class Sorted<int>;

int main()

{

Sorted<int> is;

Urand<47> rand1;

for(int k = 0; k < 15; k++)

is.push_back(rand1());

is.sort();

for(int l = 0; l < is.size(); l++)

cout << is[l] << endl;

} ///:~

 

在这个例子中,外部实例化其实并没有真的做了什么,没有它们,程序一样会照常运行,外部实例化只是说明了需要外部的控制。

外部实例化模板功能 控制模板实例化

通常模板只有在需要的时候才实例化,对函数模板来说,这就意味着你调用它的那一时刻,但对类模本来说,它就更加明细化了,只有在我们使用到模板中的某个函数时,函数才会被实例化,换句话说:只有我们用到的成员函数被实例化了,例如:

//: C03:DelayedInstantiation.cpp

// Member functions of class templates are not

// instantiated until they're needed.

class X

{

public:

void f() {}

};

class Y

{

public:

void g() {}

};

template <typename T> class Z

{

T t;

public:

void a() { t.f(); }

void b() { t.g(); }

};

int main()

{

Z<X> zx;

zx.a(); // Doesn't create Z<X>::b()

Z<Y> zy;

zy.b(); // Doesn't create Z<Y>::a()

} ///:~

这里,即使在模板中,它意图使用T的两个成员函数f()和g(),但事实是:程序的编译过程说明了它只有在遇到Z<X>::a()时,才真正的外部实例化了zx,同样我们也可以解释Z<Y>.

包含VS分离模式 关键字export 模板编程习惯用语 深究-第归模板

总结:当你试图使用模板来编写自己的代码时,你会发现C++的一个重大缺陷,尤其是STL代码,你会得到编译错误信息,对编译器而言,它根本无法抵御那喷涌而来的不计其数也无法预测的原文,过一小会儿,你可以改编代码(尽管这看上去有一点野蛮),让人值得安慰的是:C++编译器从中得到一些好处,先前,它们只是能告所你实例化模板的那段代码是错误的,但现在它们可以告所你模板定义中引发这场错误的根结所在。

下面是模板暗含的接口的话题。即使关键字template声明了:“我支持一切类型”,模板定义中的代码也必须有对某些针对性操作和成员函数的支持,那就是接口。所以在现实中,一个模板定义可以说:“我支持各种类型然而包含这样的接口”,如果仅仅想让编译器在你试图实例化模板对你说上一句“对不起,你想要实例化的模板中含有我不支持的接口”的话,什么都变得简单了。JAVA语言就有称作接口的特征,对此而言是非常完美的,(然而JAVA没有参数类型机制),如果有一天你能看到C++也包含这种特性的时候,那一定是很久以后的事了,编译器做的再好也只能报出实例化模板错误,然后你只能磨磨自己的牙齿,找到第一行报错的地方,开始修改它。

 

写在最后话:我翻译这篇文章的原因很简单,我看不懂它,自我学计算机以来我还没见过这么难学的东西。但我从不怀疑我的理解能力,现在这篇译文完全代表了我的思想,可以说,我完全是按照自己的思想流程来翻译的,我怎么想得就怎么翻,我想作者的目的也不过如此了。如果这篇翻译能帮助更多的人理解和学习C++模板,那将是我最开心和高兴的事,但我事先声明,我不希望看到有人用它来赚钱!

本文地址:http://com.8s8s.com/it/it3330.htm