Click here to Skip to main content
15,504,146 members
Articles / Programming Languages / C++
Article
Posted 28 May 2001

Tagged as

Stats

3.8M views
13.8K downloads
227 bookmarked

CString-clone Using Standard C++

Rate me:
Please Sign up or sign in to vote.
4.93/5 (128 votes)
7 Dec 2011CPOL4 min read
A Drop-In replacement for CString that builds on the Standard C++ Library's basic_string template

Introduction

As much as I use and appreciate the Standard C++ Library, I've never liked its string template - basic_string<>. At times, it seems the designers went out of their way to make it difficult to use.

On the other hand, I've always loved the ease of use of MFC's CString class. It checks for NULL pointers, implicitly converts to const TCHAR*, and has some very handy member functions (Format, Load, etc.) that make string programming a breeze. But of course, I don't want to use MFC anymore. In fact, I don't want to rely on any proprietary library because I want portability.

Therefore I decided to combine the best of both worlds and create:

CStdString

This is a class (a template instantiation actually) that derives from from basic_string<TCHAR>. To the basic_string it adds the entire CString API. You get CString ease of use with 100% basic_string compatibility. In short, a CStdString object is a basic_string that (with very few exceptions (noted below) it is also a drop-in replacement for CString. The best part of this is that both APIs (basic_string and CString) are well known and well documented.

I originally submitted this article to another code site (which shall remain nameless :)) a few years ago. I like CodeProject so much I thought I'd submit it here too. I have used this class in almost every professional project I've done over the past 4 years. It has proven to be the single most useful piece of code I've ever written. It is also extensively debugged. I hope you like it. If you ever have any problems with it, please e-mail me. I'm happy to help.

I provided a simple source application here to prove some of the CString functions work but it's really just a token. The list of sample projects out there that use CString and/or basic_string is massive.

Features

  • Drop in Replacement for CString (see below for exceptions)
  • Two instantiations available at all times -- wchar_t-based version CStdStringW and char-based version CStdStringA. The name CStdString is just a typedef of one of these two.
  • Safely checks for NULL string pointer inputs (like CString) in all functions
  • Extra constructors and assignment operators to automatically convert between wide (wchar_t-based) and thin (char-based) strings for you.
  • Implicit conversion to c_str(). The C++ committee doesn't like this but I sure do.
  • Builds on several platforms, including Windows, Unix and Linux. Works with several implementations of the Standard C++ Library, including Dinkumware, GNU, CodeWarrior, and STLPort.
  • Win32 builds give you some extra goodies like UNICODE/MBCS conversion macros (just like MFCs) as well as member functions for persisting CStdString objects to and from DCOM IStreams.
  • Makes no use of any implementation details of the base class template (basic_string)
  • The derived template adds no member data to basic_string and adds no virtual functions

There are a couple of issues about this code of that I should point out.

CString Compatibility

I was unable to exactly reproduce the CString API. There are a two functions that both CString and basic_string; share, but implement differently. In these cases, I felt it best to make CStdString behave like basic_string (the base class) rather than CString. To be specific.

  • CStdString::operator[] returns characters by value (unlike CString which returns them by reference)
  • The constructor that takes a character and a count takes them in the order (count, value) which is the opposite of the order CString declares them. That's the order that basic_string<>; needs and it was impossible to implement both versions.

There were also two CString functions I could not implement at all -- LockBuffer and UnlockBuffer.

Deriving From basic_string<>

The template I wrote derives from basic_string, a class template without a virtual destructor. Any introductory text to C++ will tell you that it is dangerous to derive from a class without a virtual destructor. It can lead to behavior that is undefined. So if you were to code the following (deleting a CStdStringA through a pointer to the base class), you would technically get undefined behavior:

C++
// assign DERIVED object to  BASE pointer
std::string* pstr = new CStdStringA("Hi"); 

// delete  DERIVED through BASE class pointer -- UNDEFINED
delete pstr;   

Personally, I don't think this is much of an issue. I mean really how often do you actually do this with string objects? I have rarely (if ever) needed to dynamically allocate a string object on the heap. And if I ever do, I won't using a base-class pointer. So if you don't do this, you'll never have to worry. In fact, even if you do code this way, I doubt you'll have any problems with CStdString. I can tell you that at least with Microsoft Visual C++, even the above code runs just fine with no errors or memory leaks. I doubt many other compilers would give you problems either. However my doubt does not impose reality on the C++ world. Caveat Emptor.

History

  • 7 Dec 2011: Updated source code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
I've been a software developer since 1990.

While my main page is out of date (and I have therefore blanked it), you can read about the CStdString pages here

http://home.earthlink.net/~jmoleary/stdstring.htm

Comments and Discussions

 
QuestionGreat, but... Pin
qiuqianren6-Dec-11 22:26
qiuqianren6-Dec-11 22:26 
AnswerRe: Great, but... Pin
Joe O'Leary7-Dec-11 4:10
Joe O'Leary7-Dec-11 4:10 
GeneralMy vote of 5 Pin
roman3136-Dec-11 21:03
roman3136-Dec-11 21:03 
GeneralMy vote of 5 Pin
Member 22124841-Dec-11 12:36
Member 22124841-Dec-11 12:36 
GeneralMy vote of 5 Pin
jjones779-Nov-11 14:21
jjones779-Nov-11 14:21 
GeneralMy vote of 5 Pin
Dean Rasmussen6-Oct-11 21:27
Dean Rasmussen6-Oct-11 21:27 
QuestionGreat class but... yes a problem with Format Pin
BadJerry16-Sep-11 6:43
BadJerry16-Sep-11 6:43 
AnswerRe: Great class but... yes a problem with Format Pin
Joe O'Leary16-Sep-11 19:21
Joe O'Leary16-Sep-11 19:21 
Hi,

Well if you get the latest version (which I can email you if you email me at the address you'll find at the top the header file StdString.h) it will fix your problem. Frankly I'm surprised your code doesn't just plain crash. What you are doing is something I generally warn against both in the header and in this article. Actually it's something Microsoft recommends against but let you get away with when you use CString. However if you get the latest version of StdString.h you will get a workaround that will let you do what you want and make your code work properly.

I go into the reason why fully inside the latest header file (search for the string "SS_SAFE_FORMAT" and you'll find a long series of comments explaining it). However. I'll try to go into it here too:

When you call function like Format() or sprintf() or anything that formats strings with variable arguments at the end, you should only supply "plain-old-datatypes" (POD) as the "..." arguments. The "%s" format specifier is expecting a const char*. In a UNICODE build it is expecting a const wchar_t*. It's a const TCHAR* and in this discussion I'll call it a "PCTSTR" for short

Now normally when you call a regular C++ function that expects a PCTSTR and you supply a CStdString object, you get away with it because the compiler is smart enough to see that the function you're calling expects PCTSTR and that CStdString has an implicit cast operator for it. So it calls that cast operator for you and you are all set.

void Foo(PCTSTR szString)
{
    // Do something with null terminated string
}

// Call the function

int main()
{
    CStdString bar(_T("Joe"));
    Foo(bar);  // works because compiler calls implicit conversion for you
    return 0;
}


But when you call Format(), the compiler can't do that. It doesn't know what type is expected. It's not smart enough to parse the format string, see the %s and realize that's what it is. That's the problem with "..." arguments (called "variadic" arguments). The compiler cannot do any type checking on them.

So what does it do? Well it supplies the first 4 bytes of whatever you pass. That's what Format() sees. If it's an int, the first 4 bytes are the int. If it's a PCTSTR, then the first 4 bytes are the pointer to that string. But if it's an object, the first 4 bytes could be ANYTHING. It all depends upon how the object is laid out in memory.

But if you've used MFC, you probably realize that it lets you get away with it. Why? Well the MFC designers created a hack of their own. The laid out CString so that the first 4 bytes of the class are always a pointer to the null-terminated string it wraps. They're always the exact same PCTSTR that operator() returns.

That's a complete hack. Unfortunately it's one I cannot reproduce with CStdString. Since CStdString is just a template, derived from whatever implementation of basic_string exists in your library, there is no way I can control the binary layout of the class.

The correct way to write this line

strC.Format(_T("%s%s"), strA, strB);


is like this:

strC.Format(_T("%s%s"), strA.GetString(), strB.GetString());


or like this:

strC.Format(_T("%s%s"), strA.c_str()(), strB.c_str());


or even like this:

strC.Format(_T("%s%s"), static_cast<PCTSTR>(strA), static_cast<PCTSTR>(strB));


In short, what you SHOULD do is let the compiler know explicitly what you are trying to pass for the "%s" argument.

But since I had so many people emailing me about this, I created my workaround (read: "hack"). It will allow you to get away with not doing any of that. However I do not recommend it for any new code as it ONLY works for Format(). But it should help you port your code with fewer changes needed.

Hope this helps. Please let me know if you have any questions.
-Joe

GeneralRe: Great class but... yes a problem with Format Pin
BadJerry17-Sep-11 0:31
BadJerry17-Sep-11 0:31 
GeneralRe: Great class but... yes a problem with Format Pin
Joe O'Leary17-Sep-11 6:42
Joe O'Leary17-Sep-11 6:42 
GeneralRe: Great class but... yes a problem with Format Pin
BadJerry26-Sep-11 2:25
BadJerry26-Sep-11 2:25 
GeneralRe: Great class but... yes a problem with Format Pin
Joe O'Leary26-Sep-11 5:56
Joe O'Leary26-Sep-11 5:56 
GeneralRe: Great class but... yes a problem with Format Pin
BadJerry26-Sep-11 7:09
BadJerry26-Sep-11 7:09 
GeneralAnother easy way converting wchar_t to char and vice versa in StdCodeCvt Pin
steveb12-Jan-11 11:04
steveb12-Jan-11 11:04 
GeneralRe: Another easy way converting wchar_t to char and vice versa in StdCodeCvt Pin
wheregone5-Jul-11 22:53
wheregone5-Jul-11 22:53 
GeneralCrashing inside ssasn Pin
Member 332703320-Oct-10 11:02
Member 332703320-Oct-10 11:02 
GeneralRe: Crashing inside ssasn Pin
Joe O'Leary20-Oct-10 14:50
Joe O'Leary20-Oct-10 14:50 
GeneralRe: Crashing inside ssasn Pin
Joe O'Leary9-Dec-11 5:41
Joe O'Leary9-Dec-11 5:41 
QuestionErrors and Warnings with VS2010 Pin
pscholl9-Feb-10 5:35
pscholl9-Feb-10 5:35 
AnswerRe: Errors and Warnings with VS2010 Pin
Joe O'Leary9-Feb-10 6:13
Joe O'Leary9-Feb-10 6:13 
GeneralMy vote of 5 and ... [modified] Pin
wheregone6-Jan-10 16:59
wheregone6-Jan-10 16:59 
GeneralWow Awesome Pin
jasonp1229-Dec-09 8:40
jasonp1229-Dec-09 8:40 
GeneralRe: Wow Awesome Pin
Joe O'Leary29-Dec-09 9:01
Joe O'Leary29-Dec-09 9:01 
GeneralOutputDebugString prints ok, but _ftprintf_s does NOT... Pin
ehaerim16-Jun-09 11:08
ehaerim16-Jun-09 11:08 
GeneralRe: OutputDebugString prints ok, but _ftprintf_s does NOT... Pin
Joe O'Leary30-Dec-09 6:07
Joe O'Leary30-Dec-09 6:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.