String handling is a common task in games programming and has traditionally been implemented using char and printf style functions. A lot of people, like myself, learned C before C++ and became very familiar with using these functions and hence felt no need to change. However these functions do have problems (see below) and safer alternative methods are available using the Standard Library (STL).
These notes show how you can switch to using safe C++ strings and still have easy to use small code.
Problems with C-style functions
The following example creates a character string to display the frames per second in the output pane of the debugger. I am presuming fps is a floating point number.
sprintf(buf,"The Frames Per Second is: %f\n", fps);
One problem with this code is that the size of the buffer is rather arbitrary and the sprintf function does not know how big the buffer is. If the buffer is not big enough a buffer overflow would occur with dire consequences! You could set the buffer size very high to avoid this but then you are wasting memory. I tended to use 2048 as it was normally big enough for most purposes but there was no really good reason and it never felt safe. Another problem is that it is easy to get the format specifier mixed up, especially when outputting many values.
So it is time to change to something that is less prone to error but is also as easy to use.
The C++ standard library contains a string type that can be used to hold our text. It is simple to use:
std::string aString("Hello World");
The above constructs a string using a constant char string. The c_str() member of the string type returns a C style string required for the OutputDebugString function.
To be able to build a string with variable values like we did in the C-style example above we need to use a string stream.
The following example shows how to output a variable using a stream:
// Place a into the stream (converts it)
stream << a;
// Output a to the string
stream >> aString;
std::cout << "The value of a is " << aString << std::endl;
This is getting there! To make it easier to use though we could write a function to take a value and return a string e.g.
std::string toString(int val)
stream << val;
Note: I could also have used ostringstream rather than stringstream.
Now we can write things like:
std::string aString("The value of a is "+toString(a) + " and b is "+toString(b));
It would be nice if our function worked for all basic types. We could write an overloaded one for each or more easily we can simply use a template to do it for us:
template <class T>
std::string toString(const T & t)
std::ostringstream oss; // create a stream
oss << t; // insert value to stream
return oss.str(); // return as a string
We can now do:
std::string aString("The value of an int is "+toString(a)+" and a float is" + toString(f);
This I believe is now as easy to use as using the old C printf style functions and is a lot safer!
Working with strings - extracting a filename from a path
As we make the switch to STL strings we need to learn new ways of manipulating strings. One example is extracting a filename from a path. Under C this was done using the _splitpath function but now we are using STL strings we need to create our own method. We can create a function that takes a path and filename contained in one string and return two strings: one for the path and one for the filename:
void SplitPath(const string& pathAndFilename, string& path, string& filename)
This function will take an input string called pathAndFilename and split it into, and return, a path and filename. One slight issue we need to contend with is forward and backward slashes in paths. Unix always uses a forward slash (/) while DOS (and hence Windows) used a backward slash (\). However DOS now accepts both forward and backward slashes. To avoid problems the first step therefore is to convert all the slashes in the path to be one way, I will make them all forward slashes. To do this we can use an STL algorithm called replace (you need to include the algorithm header to use this)
I have created a copy of the incoming pathAndFilename string in order to manipulate it. The replace function takes two iterators between which it will carry out a replace of all backward slashes with forward slashes. Note that the backward slash has to be written as a double slash otherwise it is interpreted as a special code e.g. you can use \n for new line.
The end of the path will be the last slash found and so we can use the string's find_last_of function to determine its index:
The type of the index is set to the type used by the STL string. Normally this will be size_t which we could use or we could cast it to an int but to be absolutely correct we use the type defined by the STL itself.
We need to be aware that the full path may not actually have a path component to it. In this case the find_last_of call will return an invalid code. This code is string::npos. If this is the case we simply return the path as empty and the filename as the whole path:
If a slash has been found we can extract the path part and filename part using the string's substr function:
the substr function requires a start index and a length. The lastSlashPos is the index of the last slash in the string so to extract the filename we need to start one character beyond it and the length will need calculating.
You now have your very own function for splitting a path up. I would recommend putting it in a utility class somewhere and reusing it in all your projects. If you need more information out of a path like the file extension, the drive letter etc. you can use similar methods to extract those.
- Earlier versions of these notes used stringstream instead of ostringstream which should work fine however I noticed a memory leak in an application and after a lot of searching discovered that it was in the ostringstream library function. It appears Microsoft have a bug in their code (see: http://connect.microsoft.com/VisualStudio/feedback/Workaround.aspx?FeedbackID=98861)
- With the C-style method we could specify precision and output width etc. using flags. Streams provide a number of functions to do the same like precision, fill and width and they also have the ability to set flags to convert to hexadecimal etc.using the setf function.
- Unicode support is via std::wstringstream.
- Microsoft have provided (Visual Studio 2005) some safe versions of the printf style functions like sprintf_s etc. that you may consider using although I would advise switching to strings where possible as these safe versions are Microsoft specific