闲人

君子性非异也 善假于物也
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

实现垫片类--上

Posted on 2005-10-06 12:04  闲人  阅读(1418)  评论(2编辑  收藏  举报

    最近读潘爱民先生翻译的《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日

下已经写好了:实现垫片类--下