Created 9 December 2002, last updated 6 February 2003
One of the greatest features of modern programming environments is also the most humble: ubiquitous stringification.
When I first worked in Java, I didn’t think much about the java.lang.Object.toString method. It seemed like a good idea, and made sense, but was sort of the low-tech sibling in the method list. It was hardly “computer science” to be able to turn an object into a string. It seemed almost like a patch over a language deficiency at first: “Why not just have a way to print objects?”
Gradually, I understood. The toString method is Java’s way of printing any object, and it’s the best way to do it. By allowing each class to define its own string representation, but through a common interface, Java gets the best of both worlds: callers have the power to query any object for a stringified version of itself, and implementers can use any techniques they want to stringify their data, all without adding a wart to the language.
This may be one of those features you don’t miss until it’s gone. I didn’t get it until I went back to C++ after using Java and Python.
Being able to always stringify objects without a lot of rigamarole makes it much easier to use those object in “natural” ways. For example, when adding richness to log messages, it’s much easier to provide more information by just squirting objects into log messages. The easier it is to use objects like this, the more it will get done. If we had to call special-purpose DescribeMe() methods all over the place, or create strings manually from the object at hand, there’d be too many places where it seemed like too much trouble, and it wouldn’t happen.
As described above, Java has the java.lang.Object.toString method, inspired by Smalltalk’s asString method.
Python provides two built-in methods for dealing with stringification: __str__() provides the “informal” (or human-readable) string for an object, and is called by the str() built-in function and the print statement. __repr__() provides the “official” (or computer-readable) string for an object, and is called by the repr() built-in function and the backquotes.
The pitfall in this ubiquitous stringification is that if the language or environment provides an implementation of toString for all objects, you might not write one yourself, and that would be a shame.
The default implementations are boring, by necessity: what information could they use to print something interesting? Generally, the default implementations will give the class and address of the object.
Write your own implementation! It isn’t hard, and once you have it, you’ll use it all over the place.
C++ doesn’t have a built-in class hierarchy to provide the toString() interface. (Its proponents claim the lack of a built-in hierarchy as an advantage, because your classes have only what you want in them).
It does have the ostream class from the Standard Template Library, though, and ostream has inserters. “Inserter” is the fancy term for the << operator. The C++ equivalent of toString() is an ostream inserter for your class. This isn’t quite as good as toString(), since it only works with ostreams, and not in other contexts where you’d like a string. I suppose you could go whole-hog into the STL and use std::string by value to emulate Java and Python. I never have.
Inserters are not actually members of your class: they are functions (which can be declared as friends of your class). This may seem a little awkward, and I suppose it is. There is one advantage: you can define inserters for classes you didn’t write. This lets you customize the appearance of someone else’s objects, or lets you add on stringification after the fact to library objects you can’t otherwise change.
operator << (std::ostream & os, CThingy & thing)
return os << "thingy " << thing.GetUniqueId();
If you have your own object hierarchy, you can provide an inserter for the base class, and put yourself in a similar position to your Java and Python brethren: a boring base implementation that you’ll be tempted to rely on rather than implementing real stringification throughout the hierarchy. If your base class has a method like toString, you can always make the ostream inserter call it (or vice-versa!).