I don’t know how “cool” this class is — it’s
not really innovation as much as hard work — but I have found it extremely
useful. I have seen attempts at this in the past that I never really liked
very much…
Basically, I love the ease of use of MFC’s CString class
but I don’t want to use MFC. In fact, I don’t want to rely on any
proprietary library’s string class becase I want portability. The Standard
C++ Library string classes (string and wstring) are OK, but they are not nearly
as easy to use as CString. I decided to combine the best of both worlds
and create:
CStdString
(or as my friend Mike named it, “Stud String”)
This is a string
class derived from the Standard C++ Library basic_string<TCHAR>. It
therefore inherits all of basic_string’s functionality and compatibility.
You can use it along with MFC, ATL, neither. I have added a few (not all)
CString conveniences that I have found most useful.
Features:
Here is a summary of the functionality:
- It provides an implicit conversion operator LPCTSTR(), just like CString, so you
don’t have to call c_str() all the time. ANSI may not like this, but I
sure do.
- Like CString, CStdString is built upon the TCHAR type and so easily changes from
UNICODE to MBCS builds.
- Since it is derived from basic_string<TCHAR>, you can use it wherever you
would use std::string or std::wsting (depending upon your _UNICODE setting).
- It fixes the assignment bug in Microsoft’s basic_string<> implementations
before version 6.0. In version 6 and later the fix unnecessary and not compiled..
- It includes the following CString-like functions: Format(), TrimRight(),
TrimLeft(), and a LoadString() equivalent..
- If you use CStdString in an MFC build (i.e. when _MFC_VER is defined), it adds
global serialization operators so you can read and write CStdStrings to CArchive
objects.
- When used in non-ATL, non-MFC builds, the header file also defines UNICODE/MBCS
conversion macros identical to those of MFC and ATL (e.g. A2CT, T2CW, etc.)
- It has no member data and requires no cleanup code, thereby avoiding the potential
danger of the fact that basic_string’s destructor is non-virtual
- Most functions are inline for speed.
- Case INsensitive comparison via the Equals() member function
- A static SetResourceHandle() member function if you want to search another HINST
first when attempting to load string resources (a thin imitation of
AfxSetResourceHandle)
- Constuctor and assignment operator that takes the new Visual C++ _bstr_t compiler
support class. This eliminates compiler ambiguity errors that you would otherwise
receive if you tried to assign a _bstr_t to a CStdString.
- Implementations of Load() and Format() are cleaner, shorter, and faster than MFC’s
- Member functions to simplify the process of persisting CStdStrings to/from
DCOM IStream interfaces (StreamSize, StreamSave, StreamLoad)
- Functional objects (e.g. StdStringLessNoCase) which allow CStdStrings to
be used as keys in STL map objects with case-insensitive comparison
- Added the ability to manually turn off the conversion macros from being
defined. Occasionally necessary in MFC/ATL builds.
New Version — Converted to a Template (Jun 22,
1999)
This new version is almost a complete rewrite of the
class. You now have both a wide and a regular version of the class
available at all times, regardless of whether your build is Unicode or
not. The regular version is called “CStdStringA” and the wide version is
called “CStdStringW”. The name CStdString is just a #define of one of
these names, depending on the _UNICODE macro setting. This new
version also includes the following fixes and changes:
- Fixed FormatV() to correctly decrement the loop counter. This was harmless
bug but a bug nevertheless. Thanks to Chris (of Melbsys) for pointing it out.
- Changed FormatV() to first try a normal stack-based array before reverting to
_alloca(). This should help us avoid crashes if Format() is called from a
catch() handler, since _alloca() may not be called from catch() handlers.
- Updated the text conversion macros to properly use code pages and to fit in
better in MFC/ATL builds. In other words, I copied Microsoft’s conversion macros again.
- New functions — equivalents of CString’s GetBuffer and GetBufferSetLength members. I prefer shorter names,
so I named them Buffer and BufferSet respectively. They are yours to rename.
- Removed the static member function named CopyString
and replaced it with a global function _sscpy(). I doubt any legacy code
depends on this but if it does I’ve left commented out versions of the
CopyString that you can use.
I have found this class invaluable for several large-scale distributed projects. I
have used it with and without both MFC and ATL. The class is portable and does not
conflict with either class library.
Issues:
There are a few of things to keep in mind when using this class.
- The reference-counting logic of MS’ basic_string implementation is unchanged.
It uses operator++ and — which are NOT thread-safe. It has never caused me
problems (and I’ve used it in several multi-threaded applications) but there you
have it. You can turn off the reference counting behavior altogether if you are
willing to edit the Microsoft <xstring> header and change one line. Find the
line that says:
enum _Mref {_FROZEN = 255};
and change the value of _FROZEN from 255 to zero.
- Even though this class fixes the MS basic_string::assign() bug, you can probably
use it with any other implementatin of basic_string. I don’t think I have used
any knowledge of the implementation.
- I have optimized a couple of the helper functions (i.e. ToLower(), ToUpper(), etc)
for speed by using C run-time library functions such as _tcslwr. This is not quite
Standard C++ portability, however, so I have left in the code, commented-out
versions of these routines which make use of the standard methods for accomplishing
the same tasks. If you need the portability and don’t mind a significant drop in
speed, just switch the comments.
- If you build your code on a Windows 95 machine, I have noticed that sometimes if
you place this class in a precompiled header of a project, you may get an internal
compiler error at build time in one of the basic_string constructors. On WinNT,
this problem does not occur. I cannot explain it except to say that I believe it
has to due with VC’s inability to properly handle default template arguments. The
workaround is to keep the class out of a precompiled header on your Win95 builds.
On one hand, I am sorry I cannot offer more help on this one. On the other hand,
an internal compiler error is by definition a problem with the compiler, not the
code. If anyone really has trouble with this, e-mail me and I’ll try help you out.
I try to keep the CodeGuru version of this class up to date. However if you
are interested, you can also check my own
personal home page for a more recent
version
Example Usage:
// You can add just about any form of string to a CStdString // with operator+() CStdString strVal1(_T("THIS IS A STRING")); OutputDebugString(strVal1 + _T("n")); strVal1 += _bstr_t(" plus a BSTR string"); strVal1 += '.'; // Some conversion functions can be chained together strVal1.ToLower().TrimRight(); // The Equals() function allows case INsensitive comparison strVal1 = _T("THIS IS A STRING"); CStdString strVal2(_T("thIs Is a sTRing")); _ASSERTE(strVal1 != strVal2); _ASSERTE(strVal1.Equals(strVal2)); // Format() works just like CString's strVal1.Format(_T("This %s a string named strVal%d"), _T("IS"), 1); OutputDebugString(strVal1 + _T("n")); // Declare an STL map class which maps strings to integers. The // keys are case insensitive, so an integer stored under the key // _T("MYKEY") could be retrieved with the value _T("mykey") typedef std::map<CStdString, int, StdStringLessNoCase> CMyMap CMyMap myMap; myMap[_T("MYKEY")] = 7; _ASSERTE(myMap.find(_T("mykey")) != myMap.end()); // If this were MFC code, we could serialize the strings to CArchives: void CMyObject::Serialize(CArchive& ar) { CStdString strVal3(_T("This is a string")); if ( ar.IsStoring() ) ar << strVal; else ar >> strVal; } // If we were implementing the IPersistStream interface on a COM // object which had a member CStdString variable named 'm_strVal', // we could take advantage of the CStdString stream functions to // greatly simplify the implementation as follows: HRESULT CMyComObject::GetSizeMax(ULARGE_INTEGER* pcbSizeMax) { pcbSizeMax->QuadPart = m_strVal.StreamSize(); return S_OK; } HRESULT CMyComObject::Save(IStream* pStream, BOOL bClearDirty) { return m_strVal.StreamSave(pStream); } HRESULT CMyComObject::Load(IStream* pStream) { return m_strVal.StreamLoad(pStream); } // If we were calling some windows function that fills out a // buffer for us we can use the Buffer() function CStdString strPath; ::GetTempPath(strPath.Buffer(MAX_PATH+1), MAX_PATH); // You can set the resource handle for loading string resources // and then load them via either the constructor or the Load() // function. CStdString::SetResourceHandle(::GetModuleHandle(NULL)); CStdString strString(IDS_STRING1); strString.Load(IDS_STRING2);
I hope that others find this class as useful as I have.
Downloads
History