Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

OStringStream, or how to stop worrying and never use sprintf again

0.00/5 (No votes)
20 Mar 2002 1  
A typesafe alternative to sprintf from the std library

Introduction

There are two main reasons why people use CString over std:string.  The first is that a CString converts easily between a BSTR and a char *.  The way to overcome this is to #include<comdef>, and cast your std::string.c_str() to a _bstr_t.  The second problem requires a little more explanation...

CString has a very handy method called Format.  This method works the same way as sprintf, which is hardly surprising as internally it takes an educated guess as to the new buffer size and then calls _vstprintf.  The problem with using sprintf yourself or relying on functions that do so is twofold.  First of all, you need to make memory allocations beyond what you need in order to minimise the possibility of a buffer over-run, a possibility you can do nothing to get rid of entirely.  Secondly, because you need to provide variables, and formatting information separately, there is no type safety, and a real cause for error.

None of this is completely life threatening, and plenty of commercial apps I am sure use sprintf with no problem.  However, it just isn't best practice.  The highly under-rated iostream library provides a much better option, in the form of ostringstream.  This class provides a buffered stream, and returns us a std::string containing the information we have streamed to it.  Because it is an iostream, we can pass any type into it so long as a stream handler exists for it.  I'm not going to cover writing your own in this article, but it is really is very easy to do, and then you can stream any types you may create.

So the basic idea is that we create an ostringstream and then pass information into it, like this:

ostringstream strstrm;
strstrm << "This is how I pass in text, but I can pass in numbers as easily - " 
        << 42 << endl;

We can then access the underlying string using the str() method, which we can chain to the strings c_str() method to get a const char * if desired.  The endl is an example of a stream modifier - it added a newline character and flushes the stream.

cout << strstrm.str();

The str method can also be passed a new value for the buffer, so we can clear it like this:

strstrm.str("");

Now we've got the basics under our belt, lets see what we can do to implement sprintf type behaviour.  Obviously we can pass in numbers, but what if we want to format them ? Well, as the following code shows, we can do a lot more than sprintf does.  We can easily switch between hex, oct and decimal, we can get bools to be printed as words ( a note, BOOL is NOT a bool, it is a typedef for an int.  iostreams will always regard BOOL to be an int, this is your fault for using BOOL, which Microsoft created for C programming, as C does not have a bool type ), we can format floating point numbers as scientific notation, etc.  Note that when we use fixed point we get 6 decimal places, if we just stream a float, we get six significant figures overall.  That is, if you add another significant figure to the left of the decimal point in the example, you'll lose a figure to the right.

Here is the code for the example, followed by it's output.

string str;
ostringstream strstrm;
strstrm << boolalpha << true << " is a bool value" << noboolalpha 
        << ", so is " << false; cout << strstrm.str() << endl;
strstrm.str("");

strstrm << "100 decimal = " << dec << 100 << ", octal = " << 100 
        << ", hex = " << hex << 100 << endl;
cout << strstrm.str();
strstrm.str("");

float f = 199.9273f;
strstrm << "A float: " << f << ", scientific notation " << f 
        << " " << scientific << f << " fixed notation " << fixed 
        << f << endl;
cout << strstrm.str();
strstrm.str("");

strstrm << "Formatted to two decimal places " << setprecision(2) 
<< f << ", formatted to %2.5f " << setprecision(5) << 
leftprec(f, 2) << endl; cout << strstrm.str();
 
true is a bool value, so is 0
100 decimal = 100, octal = 144, hex = 64
A float: 199.927, scientific notation 199.927 1.999273e+002 fixed notation 199.927307
Formatted to two decimal places 199.93, formatted to %2.5f 99.92731

Now, there is one little trick in here.  The modifiers boolalpha/noboolalpha turn alpha booleans off and on.    dec/octal/hex changes the number base (setbase(8/10/16) does the same), scientific/fixed change the way floats are shown (uppercase/nouppercase allows you to set the case of the E in scientific notation, and showpos/noshowpos allows you to force a + sign for positive numbers ), and setprecision allows you to set the precision to the right of the decimal point.  However, no method is provided to set the precision to the left of the decimal point. 

My first thought in providing such a method was to use the % operator, however it does not work with floats ( why? ).  So in order to provide such a method I had to write the following macro.

#define leftprec(x,y) (x - (((int)floor(x) - (((int)floor(x))% ((int)(pow(10,y)))))))

And people ask me why I think macros are ugly....  Anyhow, you can use setprecision to set precision to the right of the decimal, then pass in the value and the number of significant characters you want to the left of the decimal.  This provides no rounding, it will just strip the extra values.  Note also you'll need to #include<math.h>

I'm afraid I built this project using VS.NET beta 2, so the provided project will not work with VC 6.  However, it is a simple task to create an empty console app and copy the main() function from the provided cpp file into it, so I don't feel it's too much of a problem.  I hope this article has made you realise some of the power provided by the iostream library, as well as dispelling any reason you may have for feeling that life cannot go on without CString.  The standard string does everything CString does, it's just that sometimes it's not as clear, because std::string provides some of this functionality through iostreams, and some through the fact it is an STL container, which means that all the algorithms provided by the STL work with it.  In effect, std::string is FAR more powerful than CString, if you know where to look.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here