Recently I did some teaching of an Advanced C++ course, for various audiences. I noticed that many people coming from a C background sometimes have a hard time abandoning the “old way” of C style programming and thinking. This is hardly new or surprising. I try, whenever possible, to point out the C++ way of doing things, where a legacy C alternative exists.
One of the obvious culprits is the infamous printf C function. That’s probably the first function to be learned by a C (and maybe even C++) programmer, and it’s a surprisingly complex function. Its prototype is something like this:
int printf (const char* format, …)
(I’m ignoring any compiler specific export directives and such, as they’re not relevant for this discussion).
(by the way, printf returns the number of characters actually printed to the console).
printf accepts a variable number of arguments, that can be anything. Format specifiers inside the format argument provide clues as to how to interpret those extra arguments. A typical printf example would be:
int x;
//...
printf("the value is %d points", x);
The %d tells printf to expect an integer as the first argument after the format string. And there are plenty of them, such as %f, %lf, %u, %s, %c and others. C programmers tend to learn the meaning of each one they actually use. In C++, the cout object is used as an alternative:
cout << "the value is " << x << " points";
At first glance, it may seem not too different, an maybe even longer. But there’s a fundamental difference between using cout and printf. printf is typeless. It has no idea what it actually gets, it just knows what it should expect. If these match then all is well. But if not – weird things can happen. For instance, what if %d is passed but the value itself is a double? Or worse, vice versa: %lf is passed (expecting a double), but an int is provided? printf has no idea this is happening. The compiler will be silent. At runtime, the value would appear wrong (in the first case, because a floating point number is interpreted differently than an integer one) and in the second case, printf will attempt to read 8 bytes for the double, where only 4 (assuming sizeof(int)==4) are supplied. It would read pass the memory of that variable, whatever is there would be part of the result.
With cout, this can never happen. cout is a global instance of ostream with operator << for every basic type. New overloads can be created for custom types. For example, assume a class Person is defined, with Person objects that need to be output in some way. We want to say:
Person p("bart simpson");
cout << p;
This works assuming the following (global) operator is defined:
ostream& operator<<(ostream& out, const Person& p) {
return out << p.GetName() << " " << p.GetAge();
}
Most C++ developer are familiar with cout. What about formatting some data into a string, instead of the console?
C has the sprintf function, working similarly to printf, but writes its output to a character array. What about C++? Are we forced to use sprintf with its lack of type safety and clunky interface? Fortunately no.
We can use the stringstream class in the same way we use cout. In this case the output goes into the stringstream. Eventually we can convert it to a normal string. Here’s an example:
stringstream ss;
ss << "the value is " << x << " points";
string result = ss.str();
You need to #include <sstream> for the compiler to be happy. Easy, right? The same rules as with any ostream. No need to allocate buffers or remember fancy format specifiers. It also works with manipulators (just as it does with cout):
ss << "the value is " << setw(5) << x << " points" << endl;
setw is an example manipulator, that makes sure the value is displayed with at least 5 characters (in this case), padding with spaces to the left of the value.
So, there you have it. sprintf is no longer needed, and so are many other C style constructs that have been carried into C++ by a necessity of compatibility.
C++ is back with a new finalized standard. I’ll talk about some of the new features in future posts.