Click here to Skip to main content
15,881,833 members
Articles / Programming Languages / C++
Article

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

Rate me:
Please Sign up or sign in to vote.
4.71/5 (28 votes)
20 Mar 20025 min read 302.7K   1.3K   44   53
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


Written By
Software Developer (Senior)
Australia Australia
Programming computers ( self taught ) since about 1984 when I bought my first Apple ][. Was working on a GUI library to interface Win32 to Python, and writing graphics filters in my spare time, and then building n-tiered apps using asp, atl and asp.net in my job at Dytech. After 4 years there, I've started working from home, at first for Code Project and now for a vet telemedicine company. I owned part of a company that sells client education software in the vet market, but we sold that and I worked for the owners for five years before leaving to get away from the travel, and spend more time with my family. I now work for a company here in Hobart, doing all sorts of Microsoft based stuff in C++ and C#, with a lot of T-SQL in the mix.

Comments and Discussions

 
QuestionMay be buffer safe, but no replacement for sprintf() Pin
Member 114961673-Mar-15 14:25
Member 114961673-Mar-15 14:25 
AnswerRe: May be buffer safe, but no replacement for sprintf() Pin
Christian Graus3-Mar-15 15:07
protectorChristian Graus3-Mar-15 15:07 
Generalboost::format Pin
Rob Caldecott14-Dec-06 5:22
Rob Caldecott14-Dec-06 5:22 
GeneralLeft precision Pin
The Gilb14-Dec-06 5:07
The Gilb14-Dec-06 5:07 
GeneralExcellent replacement for sprintf, Format Pin
supermyers21-Sep-06 6:11
supermyers21-Sep-06 6:11 
Questionstd::streams still slow and buggy? Pin
WalterSchreppers30-Jun-06 5:55
WalterSchreppers30-Jun-06 5:55 
General#include Pin
deterium10-Feb-06 6:57
deterium10-Feb-06 6:57 
GeneralRe: #include Pin
Christian Graus10-Feb-06 10:10
protectorChristian Graus10-Feb-06 10:10 
GeneralFormat the precison to 3 digits. Pin
Sandeep. Vaidya30-Jan-06 2:38
Sandeep. Vaidya30-Jan-06 2:38 
GeneralRe: Format the precison to 3 digits. Pin
Christian Graus30-Jan-06 9:19
protectorChristian Graus30-Jan-06 9:19 
Generalsnprintf is buffer overrun safe Pin
nicophonic11-Jan-06 8:35
nicophonic11-Jan-06 8:35 
GeneralRe: snprintf is buffer overrun safe Pin
dibeas1-Jun-06 0:14
dibeas1-Jun-06 0:14 
GeneralThanks alot for your tutorial Pin
ionutcelgroaznic10-May-05 10:43
ionutcelgroaznic10-May-05 10:43 
Generalto round or not to round, that is the Q Pin
wasouthpnt31-Jan-05 6:30
wasouthpnt31-Jan-05 6:30 
GeneralRe: to round or not to round, that is the Q Pin
kirkkorver11-Jul-05 12:19
kirkkorver11-Jul-05 12:19 
GeneralLegacy code Pin
an_phu27-Oct-04 8:08
an_phu27-Oct-04 8:08 
GeneralRe: Legacy code Pin
Christian Graus27-Oct-04 9:25
protectorChristian Graus27-Oct-04 9:25 
Generala wee bit of silliness Pin
Anonymous21-Sep-04 17:47
Anonymous21-Sep-04 17:47 
GeneralRe: a wee bit of silliness Pin
Christian Graus22-Sep-04 10:19
protectorChristian Graus22-Sep-04 10:19 
GeneralRe: a wee bit of silliness Pin
Anonymous28-Sep-04 19:47
Anonymous28-Sep-04 19:47 
"plenty of commercial apps I am sure use sprintf with no problem" yet now you think it unsafe

your words were "if desired" not "if you need it"
"chain to the strings c_str() method to get a const char * if desired" Six or one or a half dozen of another. The meaning is the same.

Tsk, tsk .Don't like critiques on your bad teaching style. How about showing why stprint and _T macros can be implemented with streams. How about showing how you'd do a "practical" real world thing like use the stringstream with message box. Dole queues are for those who can't react properly to others criticism.

GeneralRe: a wee bit of silliness Pin
Christian Graus29-Sep-04 10:08
protectorChristian Graus29-Sep-04 10:08 
GeneralRe: a wee bit of silliness Pin
Anonymous6-Oct-04 21:14
Anonymous6-Oct-04 21:14 
GeneralRe: a wee bit of silliness Pin
Christian Graus7-Oct-04 9:54
protectorChristian Graus7-Oct-04 9:54 
GeneralRe: a wee bit of silliness Pin
Angus Comber20-Nov-07 1:18
Angus Comber20-Nov-07 1:18 
GeneralRe: a wee bit of silliness Pin
Will Nolan17-May-08 4:01
Will Nolan17-May-08 4:01 

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.