无类型的模板参数
这里有一个用来产生随机数的类,它可以接受一个的数字,然后通过重载()符号,来产生一个符合要求的随机数。具体代码如下:
//: C03:Urand.h
// Unique random number generator
#ifndef URAND_H
#define URAND_H
#include <cstdlib>
#include <ctime>
template<int upperBound>
class Urand
{
int used[upperBound];
bool recycle;
public:
Urand(bool recycle = false);
int operator()(); // The "generator" function
};
template<int upperBound>
Urand<upperBound>::Urand(bool recyc)
: recycle(recyc)
{
memset(used, 0, upperBound * sizeof(int));
srand(time(0)); // Seed random number generator
}
template<int upperBound>
int Urand<upperBound>::operator()()
{
if(!memchr(used, 0, upperBound))
{
if(recycle)
memset(used,0,sizeof(used) * sizeof(int));
else
return -1; // No more spaces left
}
int newval;
while(used[newval = rand() % upperBound])
; // Until unique value is found
used[newval]++; // Set flag
return newval;
}
#endif // URAND_H ///:~
类Urand工作原理是这样的:它保留了一个对所有可以取到的数字在随即空间里的MAP(映射)(随机数的上限通过参数传给模板),并且给使用过的数字打上标记。可控的构造函数允许你在所有使用完所有资源后,重新回收再用。请注意:这个类是从优化速度的角度出发来实现功能的,所以它为所有实体都分配了映射空间。
缺省模板参数
关键字typename的使用
看下面的代码:
//: C03:TypenamedID.cpp
// Using 'typename' to say it's a type,
// and not something other than a type
template<class T> class X
{
// Without typename, you should get an error:
typename T::id i;
public:
void f() { i.g(); }
};
class Y
{
public:
class id
{
public:
void g() {}
};
};
int main()
{
Y y;
X<Y> xy;
xy.f();
} ///:~
从模板中的定义可以看出:我们假设了类T中含有某个嵌套在类内部的也可以用来声明变量的类型(type)——id。id可以是这个类T中的一个对象,通常情况下,你可以直接对id进行操作,但你不可以用id再creat一个其他的对象。然而在这里,在typename的帮助下,我们却做到了这一点。程序中Id被当作一个确确实实存在的类型来处理了,但如果我们丢掉了typename关键字,编译器就无法知道id 究竟是个什么东西了。
(在没有typename的时候)编译器会选择并区分我们在模板中定义的东西究竟是什么类型的,于是乎,它会把id当成其他的东西而不是一个类型(type),换句话说:它总是更乐意把标示看成一个对象(甚至是可变的私有类型),一个枚举或者什么类似的声明。当然,它不会的——也不能——就把它看成是一个(类型)type,它没这么聪明,当我们把id作为一个type使用的时候,编译器会无法理解的。
Typename关键字告诉了编译器把一个特殊的名字解释成一个类型,在下列情况下必须对一个name使用typename关键字:
1. 一个唯一的name(可以作为类型理解),它嵌套在另一个类型中的。
2. 依赖于一个模板参数,就是说:模板参数在某种程度上包含这个name。当模板参数使编译器在指认一个类型时产生了误解。
保险期间,你应该在所有编译器可能错把一个type当成一个变量的地方使用typename。就像上面那个例子中的T::id,因为我们使用了typename,所以编译器就知道了它是一个类型,可以用来声明并创建实例。
给你一个简明的使用指南:如果你的类型在模板参数中是有限制的,那你就必须使用typename.
用typename自定义一个类型
要知道typename关键字不会自动的typedef,
typename Seq::iterator It;
只是声明了一个Seq::iterator类型的变量,如果你想定义一个新类型的话,你必须这样:
typedef typename Seq::iterator It;
使用typename来代替class
详细介绍了typename的使用方法之后,我们现在就可以选择typename来取代class声明,这样可以增加程序的清晰度。
//: C03:UsingTypename.cpp
// Using 'typename' in the template argument list
template<typename T> class X { };
int main()
{
X<int> x;
} ///:~
你当然也会看到许多类似的代码没有使用typename关键字,因为模板概念诞生之后很久了,才有了typename关键字的加入。
函数模板
一个模板类描述的是一个无限的类的集合,你看到的是这些类中最普遍的地方。当然,C++也支持无限集合函数的概念,有是这是很有用的, 这些东西实质上是一样的,除非你就是想声明一个函数而不是一个类。
你可能已经知道了,我们要创建模板函数的原因,就是因为我们发现很多函数看上去是完全一样的,除了他们所处理的类型不同以外。并且,一个函数模板在许多地方都是非常有用的,就像我们在第一个例子中阐述的和我们将看到的第二个例子,它使用了容器(containers)和陈述(iterators)。
字符串转换系统
//: C03:stringConv.h
// Chuck Allison's string converter
#ifndef STRINGCONV_H
#define STRINGCONV_H
#include <string>
#include <sstream>
template<typename T>
T fromString(const std::string& s)
{
std::istringstream is(s);
T t;
is >> t;
return t;
}
template<typename T>
std::string toString(const T& t)
{
std::ostringstream s;
s << t;
return s.str();
}
#endif // STRINGCONV_H ///:~
这里是测试程序,它包括了标准库complex的使用:
//: C03:stringConvTest.cpp
#include "stringConv.h"
#include <iostream>
#include <complex>
using namespace std;
int main()
{
int i = 1234;
cout << "i == \"" << toString(i) << "\"\n";
float x = 567.89;
cout << "x == \"" << toString(x) << "\"\n";
complex<float> c(1.0, 2.0);
cout << "c == \"" << toString(c) << "\"\n";
cout << endl;
i = fromString<int>(string("1234"));
cout << "i == " << i << endl;
x = fromString<float>(string("567.89"));
cout << "x == " << x << endl;
c = fromString< complex<float> >(string("(1.0,2.0)"));
cout << "c == " << c << endl;
} ///:~
输出结果是:
i == "1234"
x == "567.89"
c == "(1,2)"
i == 1234
x == 567.89
c == (1,2)
内存分配系统
在更安全使用malloc()、calloc()和realloc()等内存分配函数的议题中,我们有许多事可以做,接下来的函数模板处理了一个函数getmem(),这个函数即可以分配新的内存空间,或者调整以分配内存空间的大小,它把新空间全部置0,并检查操作是否成功。这样,你只需要告诉它需要多少空间就行了,还减少了程序出错的可能。
//: C03:Getmem.h
// Function template for memory
#ifndef GETMEM_H
#define GETMEM_H
#include "../require.h"
#include <cstdlib>
#include <cstring>
template<class T>
void getmem(T*& oldmem, int elems)
{
typedef int cntr; // Type of element counter
const int csz = sizeof(cntr); // And size
const int tsz = sizeof(T);
if(elems == 0)
{
free(&(((cntr*)oldmem)[-1]));
return;
}
T* p = oldmem;
cntr oldcount = 0;
if(p)
{
// Previously allocated memory
// Old style:
// ((cntr*)p)--; // Back up by one cntr
// New style:
cntr* tmp = reinterpret_cast<cntr*>(p);
p = reinterpret_cast<T*>(--tmp);
oldcount = *(cntr*)p; // Previous # elems
}
T* m = (T*)realloc(p, elems * tsz + csz);
require(m != 0);
*((cntr*)m) = elems; // Keep track of count
const cntr increment = elems - oldcount;
if(increment > 0)
{
// Starting address of data:
long startadr = (long)&(m[oldcount]);
startadr += csz;
// Zero the additional new memory:
memset((void*)startadr, 0, increment * tsz);
}
// Return the address beyond the count:
oldmem = (T*)&(((cntr*)m)[1]);
}
template<class T>
inline void freemem(T * m) { getmem(m, 0); }
#endif // GETMEM_H ///:~
为了能够清空新的内存空间,程序分配了一个计数器来记录有多少个内存块被分配了,typedef cntr就是这个计数器的类型。
有一个指针的引用(oldmem)非常关键,因为在我们分配新内存空间的时候,原来的内存头指针就改变了,这个可以帮我们找回头指针。
如果参数传递的是0,这块内存就被释放掉,这是附加功能freemem()所借用的。
你会发现getmem的操作是相当底层的,这里有许多类型和字节的操作,例如,指针oldmem并没有指向内存的开始空间,它把内存的起始空间让给计数器使用。所以,当我们要free()这块内存,getmem()必须倒退这个指针cntr所占用的字节数,因为oldmem是一个T*,它必须首先被转换成cntr*,然后索引倒退一个位置,最后在该地址执行free():
free(&(((cntr*)oldmem)[-1]));
类似的,如果预先分配过内存,getmem()也必须先拿到目前内存的分配情况,然后再重新计算调用realloc()的方法。如果尺寸增加了,为了清空新的地址空间,我们就必须算出,使用memset的起始地址,最后,oldmem的地址依然是越过计数器的地址空间。
oldmem = (T*)&(((cntr*)m)[1]);
重申:因为oldmem是一个对指针的引用,它将可以改变外界传进来的任何参数
这里有一个测试getmem()的程序:
//: C03:Getmem.cpp
// Test memory function template
#include "Getmem.h"
#include <iostream>
using namespace std;
int main()
{
int* p = 0;
getmem(p, 10);
for(int i = 0; i < 10; i++)
{
cout << p[i] << ' ';
p[i] = i;
}
cout << '\n';
getmem(p, 20);
for(int j = 0; j < 20; j++)
{
cout << p[j] << ' ';
p[j] = j;
}
cout << '\n';
getmem(p, 25);
for(int k = 0; k < 25; k++)
cout << p[k] << ' ';
freemem(p);
cout << '\n';
float* f = 0;
getmem(f, 3);
for(int u = 0; u < 3; u++)
{
cout << f[u] << ' ';
f[u] = u + 3.14159;
}
cout << '\n';
getmem(f, 6);
for(int v = 0; v < 6; v++)
cout << f[v] << ' ';
freemem(f);
} ///:~
函数模板中的类型归纳
一个非常简单但很有用的例子:
//: :arraySize.h
// Uses template type induction to
// discover the size of an array
#ifndef ARRAYSIZE_H
#define ARRAYSIZE_H
template<typename T, int size>
int asz(T (&)[size]) { return size; }
#endif // ARRAYSIZE_H ///:~
没有使用sizeof()操作,却能在编译阶段指出数组的大小,你可以有更多简明的方法来计算编译时数组的大小。
//: C03:ArraySize.cpp
// The return value of the template function
// asz() is a compile-time constant
#include "../arraySize.h"
int main()
{
int a[12], b[20];
const int sz1 = asz(a);
const int sz2 = asz(b);
int c[sz1], d[sz2];
} ///:~
当然,程序正常运行的前提是:数组在定义时就已经给出了大小。
取得一个已经实例化的函数模板的地址
有很多地方,你都需要取得函数入口地址,例如,你可能有一个函数,它的参数是一个指向另一个函数的指针,当然了,这个被指的函数有可能是从一个模板中生成的,所以,你需要一种方法来取得这样的函数地址。
//: C03:TemplateFunctionAddress.cpp
// Taking the address of a function generated
// from a template.
template <typename T> void f(T*) {}
void h(void (*pf)(int*)) {}
template <class T>
void g(void (*pf)(T*)) {}
int main()
{
// Full type exposition:
h(&f<int>);
// Type induction:
h(&f);
// Full type exposition:
g<int>(&f<int>);
// Type inductions:
g(&f<int>);
g<int>(&f);
} ///:~
这个例子讲述了许多的不同的主题。首先,即使你在使用模板,类型必须匹配——函数h()取了一个指向函数指针,这个函授接受int类型的参数返回void类型。而这正是f的特点。第二,需要函数指针作为参数的这个函数本身也可以是一个模板,就像g一样。在main()函数中,你可以看到类型归纳同样也在这里工作。第一次调用h()明确的给出了模板参数f,但是从h()中看到它仅仅处理使用int型变量做参数的函数的函数指针,那一部分可以由编译器来归纳。G()的使用还要有意思些,因为这里有两个模板要使用。编译器不能归纳出来这个类型,也就什么都不会做,但如果f和g都赋成int,其它的对编译器来说也就好办了。
模板中的成员类
向STL系列中应用函数
假设你想使用一个STL系列的容器,并且向容器中包含的所有对象应用一个函数,我之所以这样说是因为一个vector可以包含各种类型的对象,你需要一个函数可以同vector协同工作,处理它所包含的各种对象:
//: C03:applySequence.h
// Apply a function to an STL sequence container
// 0 arguments, any type of return value:
template<class Seq, class T, class R>
void apply(Seq& sq, R (T::*f)())
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
{
((*it)->*f)();
it++;
}
}
// 一个参数,返回值不定
template<class Seq, class T, class R, class A>
void apply(Seq& sq, R(T::*f)(A), A a)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
{
((*it)->*f)(a);
it++;
}
}
//两个参数,返回值不定
template<class Seq, class T, class R,
class A1, class A2>
void apply(Seq& sq, R(T::*f)(A1, A2),
A1 a1, A2 a2)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
{
((*it)->*f)(a1, a2);
it++;
}
}
// 诸如此类, 传递最多的类似的参数 ///:~
apply()函数模板使用了对容器类的一个引用和一个容器类内部成员函数的成员指针。它使用一个iterator在Stack中移动定位向各个对象套用这个函数。
注意这里没有任何STL的头文件包含在applySequence.h中,所以它在与STL系列共同使用时,没有限制。当然,在使用的时候,考虑到iterator的特殊性,我们也只能把它应用到STL系列中了(别忘了,我们必须使用iterator)。
你可以看到,这里有不止一个的apply(),因此它可以重载函数模板,每一个版本都使用了不同数量的参数,因为它是一个模板,这些参数可以为任何类型,唯一的限制是:这不是超级模板可以为你创建出新的模板。
测试一下我们不同版本的apply()。
//: C03:Gromit.h
// The techno-dog. Has member functions
// with various numbers of arguments.
#include <iostream>
class Gromit
{
int arf;
public:
Gromit(int arf = 1) : arf(arf + 1) {}
void speak(int)
{
for(int i = 0; i < arf; i++)
std::cout << "arf! ";
std::cout << std::endl;
}
char eat(float)
{
std::cout << "chomp!" << std::endl;
return 'z';
}
int sleep(char, double)
{
std::cout << "zzz..." << std::endl;
return 0;
}
void sit(void) {}
}; ///:~
现在,函数apply()可以同vector<Gromit*>协同使用来制作一个容器,调用成员函数和被包含的对象:
//: C03:applyGromit.cpp
// Test applySequence.h
#include "Gromit.h"
#include "applySequence.h"
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<Gromit*> dogs;
for(int i = 0; i < 5; i++)
dogs.push_back(new Gromit(i));
apply(dogs, &Gromit::speak, 1);
apply(dogs, &Gromit::eat, 2.0f);
apply(dogs, &Gromit::sleep, 'z', 3.0);
apply(dogs, &Gromit::sit);
} ///:~
尽管apply的定义有些复杂,新手未必能完全理解,但它的使用简洁明了,新手也知道该怎么用,我想,这就是那些为自己的程序而奋斗所为达到的目标吧:无需知道细节,只需知道实现自己的目标即可。
模板的模板
//: C03:TemplateTemplate.cpp
#include <vector>
#include <iostream>
#include <string>
using namespace std;
// As long as things are simple,
// this approach works fine:
template<typename C>
void print1(C& c)
{
typename C::iterator it;
for(it = c.begin(); it != c.end(); it++)
cout << *it << " ";
cout << endl;
}
// Template-template argument must
// be a class; cannot use typename:
template<typename T, template<typename> class C>
void print2(C<T>& c)
{
copy(c.begin(), c.end(),
ostream_iterator<T>(cout, " "));
cout << endl;
}
int main()
{
vector<string> v(5, "Yow!");
print1(v);
print2(v);
} ///:~
成员函数模板
事实上,我们也可以把apply()作为一个类中的成员函数模板,这样可以使声明更加清晰:
dogs.apply(&Gromit::sit);
作为容器类中的一个成员,apply()的定义被证明是非常清晰的,为了完成这个,我们需要从现存STL系列容器中继承一个新的容器,把我们的新函数加到这个容器中去。当然,为了具有最好的适应性,我们将使用STL系列的容器,并且,必须使用模板的模板来做这项工作,告诉编译器一个模板参数是即上是一个模板,而它自身作为一个类型参数也可以被初始化。看下面:
//: C03:applyMember.h
// applySequence.h modified to use
// member function templates
template<class T, template<typename> class Seq>
class SequenceWithApply : public Seq<T*>
{
public:
// 0 arguments, any type of return value:
template<class R>
void apply(R (T::*f)())
{
iterator it = begin();
while(it != end())
{
((*it)->*f)();
it++;
}
}
// 1 argument, any type of return value:
template<class R, class A>
void apply(R(T::*f)(A), A a)
{
iterator it = begin();
while(it != end())
{
((*it)->*f)(a);
it++;
}
}
// 2 arguments, any type of return value:
template<class R, class A1, class A2>
void apply(R(T::*f)(A1, A2),
A1 a1, A2 a2)
{
iterator it = begin();
while(it != end())
{
((*it)->*f)(a1, a2);
it++;
}
}
}; ///:~
因为他们是类的成员,所以apply()函数就不需要那么多参数了,并且iterator所属类也不需要被特殊指定。当然,begin()和end()也是新类的成员函数了,这一切看上去都那么清晰,明了。然而,基本的代码仍是一样的
//: C03:applyGromit2.cpp
// Test applyMember.h
#include "Gromit.h"
#include "applyMember.h"
#include <vector>
#include <iostream>
using namespace std;
int main()
{
SequenceWithApply<Gromit, vector> dogs;
for(int i = 0; i < 5; i++)
dogs.push_back(new Gromit(i));
dogs.apply(&Gromit::speak, 1);
dogs.apply(&Gromit::eat, 2.0f);
dogs.apply(&Gromit::sleep, 'z', 3.0);
dogs.apply(&Gromit::sit);
} ///:~
从概念上讲:你现在是从dogs这个容器中调用apply()这个方法了。
虚模板成员函数在模板类中的嵌套是不允许的
模板的特殊化
全局的特殊化
局部特殊化
一个特殊化的例子
你可以像使用普通类的方法来使用模板类,这一点是毫无疑问的,例如:你可以继承、可以创建一个从现有模板继承过来的并已经初始化的模板。如果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++也包含这种特性的时候,那一定是很久以后的事了,编译器做的再好也只能报出实例化模板错误,然后你只能磨磨自己的牙齿,找到第一行报错的地方,开始修改它。