前一阵因为要把我自己的一个通用函数库转为UNICODE,才发现原来WINDOWS/VC的解决方式也不完全好用。基本上,如果你定义了UNICODE/_UNICODE,所有的函数默认都是UNICODE的了。但是往往我还需要做一些ANSI字符串的操作,譬如从非UNICODE的文件里读取string,有些变量还必须是stl::string或者char*,就会遇到一些不便之处。举例来说,我有一个函数把一块内存存为文件:
bool SaveToFile(const string& filename, const char* data, int size)
{
FILE* f = fopen(filename.c_str(), “bw”);
if (f == NULL) return false;
bool res = fwrite(data, 1, size, f) == size;
fclose(f);
return res;
}
现在想使这个函数转为在UNICODE/ANSI下都能用,怎么办呢?如果按照WINDOWS的标准办法,可能是:
#ifdef UNICODE
typedef string tstring;
#else
typedef wstring tstring;
#endif
bool SaveToFile(const tstring& filename, const char* data, int size)
{
FILE* f = _tfopen(filename.c_str(), _T(“bw”));
...
}
但是这样的话,在编译选项为UNICODE时,如果想传入一个ANSI的文件名,就必须在调用之前进行转换,增加一道麻烦。
想了一想,决定用template来解决问题。于是定义函数如下:
template<class _S, class _E> bool SaveToFile(const _S& filename, const _E* data, int size)
不过这样一来,C++ runtime的fopen就不知道该用哪个了。如果还是用_tfopen,用UNICODE编译时就只能传UNICODE的文件名,仍然不能解决问题。到这时就感觉到_tfopen的局限了,就是它的参数完全由编译选项决定了,不能任意选择。
再想了一想,没有办法,只好再用template写一个fopen的wrapper如下:
template<class _Char> __inline FILE* my_fopen(const _Char *, const _Char *) { assert(false); }
template<> __inline FILE* my_fopen<char>(const char* fileName, const char* attr)
{ return fopen(fileName, attr); }
template<> __inline FILE* my_fopen<wchar_t>(const wchar_t* fileName, const wchar_t* attr)
{ return _wfopen(fileName, attr); }
这样不管用const char*或者const wchar_t*都可以调my_fopen。于是前面的函数可以重写如下:
template<class _S, class _E> bool SaveToFile(const _S& filename, const _E* data, int size)
{
_S attr; attr += 'w'; attr += 'b';
FILE* f = my_fopen(filename.c_str(), attr.c_str());
if (f == NULL) return false;
bool res = fwrite(data, sizeof(_E), size, f) == size;
fclose(f);
return res;
}
现在这个SaveToFile就可以传入任意类型的字符串了。
回顾一下,如果标准头文件里有这样的wrapper,或者_tfopen本身不是#define而是template function,用户就没有这么麻烦了。当然,这种想法或许不符合当时的历史情况。