从第一次使用STL到现在也有四年了,说来惭愧,以前一直只是现用现查,还从来没有仔细地读一读STL的文档。前两天偶尔看到一个别人推荐的链接(http://www.sgi.com/tech/stl/),才想起应该看看,一读之下,果然大有收获,不禁慨叹大师就是大师,这STL真是写的出神入化,把C++的template发挥到了极致。
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
先说说STL的Concept。譬如最基本的一个InputIterator,按照STL的结构,只要满足一些条件的都可以是InputIterator,如能够做++i,能dereference: *i,等等。其实这些概念也可以用一个类来表示,比方定义如下:
class InputIterator
{
virtual InputIterator& operator++() = 0;
virtual InputIterator& operator*() = 0;
...
}
但是单纯的类结构会使程序很没有灵活性。其它类型的Interator如果要具备InputIterator的属性必须从这个InputIterator派生,写起来非常的awkward。利用template写的STL,如
template<class InIt, class T> InIt find(InIt first, InIt last, const T& val);
只要你给first, last能做++,value type能比较,它就能正确工作。
从这里可以看出template在设计通用类、算法的灵活性。为了你的通用类、算法能被最大限度地利用,你应该assume被操作对象最少的属性。但是往往不同的函数要求操作对象应当具备的属性不一样。如果没有template,你就得象MFC那样用一个CObject作为万物之源(所有被操作对象的祖先),而且这个CObject几乎得无所不包;或者你也可以根据具体的函数需要象前面那样定义InputIterator类,ForwardIterator类,RandomIterator类。。。但是这些类的功能之间很可能有overlap,很难将它们的关系理清楚。更不用说C的原始数据类型如指针就无法操作了。
再举个活例子吧,也是关于我改写一些通用函数的。以前我有两个函数,分别把文件数据load进vector或string,最初是这么写的:
bool LoadFromFile(const string& filename, vector<char>& data)
{
fstream is;
is.open(filename.c_str(), ios::in | ios::binary);
... //Get length
data.resize(length);
is.read((char*)&data[0], length);
...
}
bool LoadFromFile(const string& filename, string& content)
{
vector<char> v;
if (!LoadFromFile(filename, v)) return false;
content.assign((const char*)v.begin(), (const char*)v.end());
return true;
}
第二个函数显然没有效率,但如果不想copy code这也是个办法。改写时发现如果用template写,何须管你是vector还是string?看看下面:
template<class _S, class _Data> bool LoadFromFile(const _S& filename, _Data& data)
{
FILE* f = NULL;
...
f = my_fopen(filename.c_str(), attr.c_str());
...
data.resize(length);
fread((char*)&data[0], 1, length, f);
...
}
现在,对这个输入的data的要求只是:
1. 有一个resize()函数。
2. 内存数据块连续,且&data[0]返回数据块的起始地址。
这样vector, string就都可以使了。
总结:
继续体会STL的精神,进一步发扬光大template。