C++的string实现MFC的CString::GetBuffer
今天一个老同学QQ留言给我。
老同学:“STL的string有没有类似MFC的CString::GetBuffer的函数?"
我当时正在搜夏娃种子没空鸟他。
过了一会,他问得更直接了:“如果调用SDK的::GetWindowText的时候,使用STL的string做为输出缓冲区,该怎么办?”
为了打发他,我毫不犹豫的回到“(LPSTR)string::c_str();”
5秒钟后,老同学:“。。。。。。”。
一看见他的一大串“点点点”,我猛然意识到我可能错了。
接着放下手头的事情,夏娃可以慢慢找,老同学可不能瞎忽悠。随后仔细想这件事,似乎还真没这么简单。
string::c_str()返回的是const char* 类型。强制转成char* 类型,是有不足的。一共有两点:
第一点显而易见的是缓冲区溢出问题,解决这个问题只要分配一个足够大的缓冲区就好了。比如在定义string类型的时候:
string str(MAX_PATH,'\0');
又或者:
string str; str.resize(MAX_PATH);
两种方法都使得string的成员变量“size”,变得足够大。这样只要保证对string::c_str()返回的地址写操作的时候不超过MAX_PATH个字节就行了。到这里,似乎问题就解决了。不过别急,刚才不是说有两点么,现在才第一点呢。如果第一点算是隐患,那么接下来的完全就是缺陷了。
假设,刚才我们调用::GetWindowText的代码片段如下:
using namespace std;
typedef basic_string<TCHAR> tsring;
tstring strWndTitle(MAX_PATH,'\0');
::GetWindowText(hWnd,(LPTSTR)strWndTitle.c_str(),MAX_PATH);
if(strWndTitle == _T("hello world"))
{
::MessageBox(NULL,NULL,NULL,MB_OK);
//do something...
}
这段代码有问题吗?咋一眼,没有问题啊。其实不然,这样的话,if里的代码块永远都执行不到!
假设hWnd所标示的窗口标题确实是为“hello world”,如果在if语句下个断点,程序跑起来断下来后,可以查看此时strWndTitle的内容确实是“hello world”,那么为什么执行不到if里面的语句块呢?为了好说明,我们再看下面的代码:
// 第一种 char* pBuff = (char*)string::c_str(); // 第二种 string::allocator_type alctor = string::get_allocator(); string::pointer pBuff = alctor.address(*(string.begin()));
这两种方式获得的pBuff指针指向的地址其实是一样的。第二种方式不常用,之所以让大家看这两种方式,是为了让大家看看string::c_str()返回的地址究竟指向哪。本质上,这两种方式是一模一样的,也就是指向string的开始迭代器。
“==”关系运算符,实际上是重载了string::compare,我们跟踪进STL的源码发现compare最后的实现是用memcmp实现的代码如下:
static int __CLRCALL_OR_CDECL compare(const _Elem *_First1, const _Elem *_First2, size_t _Count) { // compare [_First1, _First1 + _Count) with [_First2, ...) return (_CSTD memcmp(_First1, _First2, _Count)); }
看着有点头大,我帮大家稍微转换下,上面调用memcmp的时候实际相当于:
memcmp(strTitle.c_str(),"hello world",strlen("hello world"));
看到这里,似乎没有什么问题,事实也的确如此。OK,我们退栈看看上层主调函数。
// 这个compare就是上面那个调用了memcmp的那个 size_type _Ans = _Traits::compare(_Myptr() + _Off, _Ptr, _N0 < _Count ? _N0 : _Count); return (_Ans != 0 ? (int)_Ans : _N0 < _Count ? -1 : _N0 == _Count ? 0 : +1);
呵呵,是不是更加头大的感觉?哈哈,下面是我给大家转化的等价代码,方便大家容易看明白:
int result = memcmp(strTitle.c_str(),"hello world",strlen("hello world")); if(result == 0) { if (strTitle.size == strlen("hello world")) { result = 0; } else { result = 1; } if (strTitle.size < strlen("hello world")) { result = -1; } }
这下清晰多了吧,从上面很容易看出,string::compare先用memcmp比较内存,再检查sting对象的size成员。尽管我们在memcmp的时候返回的是0,但是由于我们的strTitle的size大于strlen("hello world"),所以最终compare将返回1,即判定strTitle大于"hello world"。
找到了原因,我们就不难理解刚才所说的为什么执行不到if语句块中的代码了。
那么解决方案也很好办,直接看码:
using namespace std;
typedef basic_string<TCHAR> tsring;
tstring strWndTitle;
strWndTitle.resize(MAX_PATH); // 类似于MFC的CString::GetBuffer(MAX_PATH);
LPTSTR pBuff = (LPTSTR)strWndTitle.c_str();
::GetWindowText(hWnd,(LPTSTR)strWndTitle.c_str(),MAX_PATH);
strWndTitle.resize(_tcslen(_T("hello world")));// 类似于MFC的CString::ReleaseBuffer()
if(strWndTitle == _T("hello world"))
{
::MessageBox(NULL,NULL,NULL,MB_OK);
//do something...
}
关键在于对pBuff写操作后,再次调用string::resize。
哈哈,这样我就可以比较完美的给老同学一个交代了。
总结:
首先调用string::resize,相当于CSting::GetBuffer,进行内存分配。
最后再次调用string::resize,相当于CString::ReleaseBuffer,进行释放闲置内存。
其间,需要注意没有释放闲置内存之前,使用string类的其他方法,会引起不可预料的意外情况。这跟MFC的CString进行GetBuffer后,没有ReleaseBuffer之前,是一样的。