最近读潘爱民先生翻译的《COM本质论》,看到了一个新名词“垫片类”(不要骂我老土,我真的是第一次见..),用来实现类型的转换。作者实现了一个_UNCC的垫片,实现了从TCHAR字符串到wchar_t类型字符串的转换。看一下原书中的例子:
HRESULT IIDFromHWND(HWND hwnd, IID& riid)
{
TCHAR szEditText[1024];
GetWindowText(hwnd, szEditText, 1024);
return IIDFromString(_UNCC(szEditText), &riid);
}
其中最后一个函数的原形是:
HRESULT IIDFromString(wchar_t *, GUID *);
而_UNCC的作用,就是把第一个参数从TCHAR*类型转换成wchat_t*类型。
抛开其他的东西不管,我们来分析一下_UNCC到底做了什么事。_UNCC的参数szEditText是一个TCHAR类型字符串,而返回的是一个wchar_t类型的字符串。这让_UNCC看起来像一个函数。现在让我们试着来实现这个函数。
实现的方法决定于这个返回的wchar_t类型的字符串是放在内存哪部分的。首先我们假定是在栈区:
wchar_t* _UNCC(TCHAR* s)
{
wchar_t wsz[100];
Translate(wsz, s)///这里假定Translate是转换函数
return wchar_t;
}
看起来是没有错的,也许你也能得到正确的结果,但是并不是每次都能正确,因为你返回的是一个指向栈区数据的指针,而栈内的数据在函数结束后就是无效的。要保持数据有效,需要把数据放到静态区,因此函数可以改成这样:
wchar_t* _UNCC(TCHAR* s)
{
static wchar_t wsz[100];
Translate(wsz, s)///这里假定Translate是转换函数
return wchar_t;
}
这样解决了上述问题,但是也不是很好的。假如你的输入参数有200个字符,那个这个程序就崩溃了。也许可以把数组定义的大一些,比如1000,或者10000。但是仍然不能保证是够用的。而且,最重要的是,占用的静态区空间,在函数结束以后也不能归还给系统!!那试着在堆上分配合适的空间来实现:
wchar_t* _UNCC(TCHAR* s)
{
wchar_t *wsz = new wchar_t[tcharstrlen(s) + 1];///这里假定tcharstrlen是TCHAR*类型的取长度函数
Translate(wsz, s)///这里假定Translate是转换函数
return wchar_t;
}
但是这样导致了一个更加烦人的内存泄露问题,你分配的空间谁去释放?
现在是否会想,如果_UNCC是类就好了,我可以在析构函数里去delete。然后就会想到,_UNCC很可能是一个函数对象,那让我们试着用函数对象实现:
class _UNCC
{
public:
_UNCC():m_p(NULL){}
~_UNCC()
{
if (NULL != m_p)
{
delete[] m_p;
m_p = NULL;
}
}
wchar_t* operator()(TCHAR* s)
{
if (s != NULL)
{
m_p = new wchar_t[strlen(s) +1];
Translate(m_p, s)///这里假定Translate是转换函数
}
else
{
m_p = new wchar_t[1];
*m_p = 0;
}
return m_p;
}
private:
wchar_t* m_p;
}
太好了!这样能完全解决上面出现的所有问题,让我们用一下试试吧:
...
return IIDFromString(_UNCC(szEditText), &riid);
编译不通过!因为函数对象也是类,类需要先实例化的,改一下吧:
...
return IIDFromString(_UNCC()(szEditText), &riid);
能用是能用了,但是和原函数的形式不一样,而且这种形式让人感到很别扭。解决这个问题也很简单,用#define把()取代掉!修改以后的函数看起来这样:
class UNCC
{
public:
UNCC():m_p(NULL){}
~UNCC()
{
if (NULL != m_p)
{
delete[] m_p;
m_p = NULL;
}
}
wchar_t* operator()(TCHAR* s)
{
if (s != NULL)
{
m_p = new wchar_t[strlen(s) +1];
Translate(m_p, s)///这里假定Translate是转换函数
}
else
{
m_p = new wchar_t[1];
*m_p = 0;
}
return m_p;
}
private:
wchar_t* m_p;
};
#define UNCC() _UNCC
这样就完全实现了这个垫片类,现在可以放心的使用了。
...
return IIDFromString(_UNCC(szEditText), &riid);
在调用这个函数的时候,首先生成一个临时的UNCC类对象实例,然后通过此实例调用重载过的()操作符来实现类型转换,并返回指针给调用他的函数使用。整个函数结束以后,临时UNCC的作用域结束,UNCC析构函数把动态分配的空间释放。
下面是一个垫片类的完整例子,垫片_A()实现了从string类型,char*类型和整数类型到字符串类型的转换,并在PrintStr函数中调试。
#include <iostream>
using namespace std;
class A
{
public:
A():m_p(NULL)
{
}
~A()
{
if (NULL != m_p)
{
delete[] m_p;
}
}
char* operator()(string s)
{
m_p = new char[s.length() + 1];
strcpy(m_p, s.c_str());
return m_p;
}
char* operator()(const char *s)
{
if (NULL == s)
{
m_p = new char[1];
*m_p = 0;
}
else
{
m_p = new char[strlen(s) + 1];
strcpy(m_p, s);
}
return m_p;
}
char* operator()(long s)
{
m_p = new char[9];
sprintf(m_p, "%ld", s);
return m_p;
}
private:
char* m_p;
};
#define _A A()
void PrintStr(const char* s)
{
cout<<s<<endl;
}
int main(int argc, char* argv[])
{
string s("I'm a C++ string");
PrintStr(_A(s));
PrintStr(_A("I'm a C string"));
PrintStr(_A(38385438));
return 0;
}
闲人,2005年10月6日
下已经写好了:实现垫片类--下