Microsoft & .NETVisual C#CString-like Class Using STL

CString-like Class Using STL


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

    Download source – 13 Kb

    Get the Free Newsletter!

    Subscribe to Developer Insider for top news, trends & analysis

    Latest Posts

    Related Stories