C++ What is wrong with using a toString() method
Asked Answered
G

5

9

I just came across this question which is about how to be able to print an object via

std::cout << x << std::endl;

As I understood, the standard way to accomplish this is to overload the ostreams << operator. However, this is adding a feature to the ostream rather than to my class.

The alternative (also given as answer to the above mentioned question) is to override the string conversion operator. However, this comes with the warning of leading to "unintentional conversions and hard-to-trace bugs".

Now i wonder if there are any drawbacks of writing a toString() method and then using it via

std::cout << x.toString() << std::endl;
Gyroplane answered 3/12, 2014 at 10:36 Comment(2)
How will you do that for an int? Or do you mean to_string?Sheathe
Because making something streamable is not the same as converting it to a string. If you want your type to be streamable, overload ostream& operator<<. If you want to make strings out of it, give it a to_string member.Hypaethral
M
5

Output streams handle output formatting as well as output. So with your toString() method clients won't be able to manage the formatting for the object the way they do for everything else:

// set specific formatting options for printing a value
std::cout << std::scientific << std::setprecision(10) << 10.0 << '\n'; // prints 1.0000000000e+01

// set formatting based on user's cultural conventions
std::cout.imbue(std::locale(""));
std::cout << 10000000 << '\n'; // depending on your system configuration may print "10,000,000"

Perhaps you don't care to allow any formatting so perhaps this won't matter.

Another consideration is that outputting to a stream doesn't require that the entire string representation be in memory at once, but your toString() method does.


Others have pointed this out, but I think a clearer way of saying it is that your classes interface is not limited to just the methods it provides, but also includes the other functions you build around it, including non-member functions such as the operator<< overloads you provide. Even though it's not a method of your class you should still think of it as part of your class's interface.

Here's an article that talks about this which perhaps you will find helpful: How Non-Member Functions Improve Encapsulation


Here's a simple example of overloading operator<< for a user defined class:

#include <iostream>

struct MyClass {
  int n;
};

std::ostream &operator<< (std::ostream &os, MyClass const &m) {
  for (int i = 0; i < m.n; ++i) {
    os << i << ' ';
  }
  return os;
}

int main() {
  MyClass c = {1000000};
  std::cout << c << '\n';
}
Moye answered 3/12, 2014 at 10:54 Comment(3)
what about abstract classes? I can have a toString()=0; but how do I declare that any child class should come with an overloaded << operator?Gyroplane
maybe i should have first read the link you posted ;) There its the first point saying that "if (f needs to be virtual) make f a member function".Gyroplane
@tobi303 Yeah, for now virtual functions must be members. It's possible that non-member functions may gain support for virtual dispatch in the future: stroustrup.com/multimethods.pdfMoye
H
4

As I understood, the standard way to accomplish this is to overload the ostreams << operator. However, this is adding a feature to the ostream rather than to my class.

And this is good thing. The smaller your class is the better. If popular C++ idiom allows you to have one more thing out of your class why not follow it?

Now i wonder if there are any drawbacks of writing a toString() method

The drawbacks are:

  • operator<< works in uniform way with builtin types (like int) and user-defined types. toString could be available for classes only
  • C++ is more heterogeneous than Java. std::string is the most popular string, but still there exist other string classes and they are used.
  • the string must be created, which potentially might come with a performance hit. If you write to stream directly you avoid it.
Heaviness answered 3/12, 2014 at 10:47 Comment(1)
These cons have much less weight that the pros of having a uniform serialization method encapuslated with any class. Why should we care about bad practices like not using std::string?Mako
H
3

They are fundamentally different things. Providing a operator<< overload effectively extends the interface of the stream, making objects of your class type streamable. Providing a toString function extends the interface of your class, making it possible to get a std::string from your class. These represent different things.

The interface of your class should entirely correspond to what it represents in your program logic (single responsibility). Rarely is toString a natural part of a class's interface. However, extending the interface of the stream to accept more objects is much more logical.

That is, in one case you are saying "Now you can stream objects of this class type". In the other case you are saying "You can turn objects of this class type into a std::string." - it just so happens that that std::string is then streamable. Think about it - does it really make sense for my Person class to have a toString function? Since when have I been able to turn people into text?

Hermy answered 3/12, 2014 at 10:49 Comment(1)
I guess whether it makes sense is a matter of taste. In my opinion it completely makes sense that any Java object can be converted to a string via toString()Gyroplane
E
1

Your first assumption is wrong. You don't need to make any changes in ostream.

An operator method like operator<< can be defined in two ways: As a method of the ostream class, taking your x object as a parameter, or as a plain old function with two parameters, taking an ostream as the first parameter, and your x object as the second parameter.

Endoparasite answered 3/12, 2014 at 10:40 Comment(0)
S
1

There is nothing wrong with a toString() function per class. It has the advantages of being more explicit, it can be made polymorph and it can be used in other situations than streaming, but you need to have a coding rule when working with a team ("was it to_string(), or str(), or streaming()?").

Overloading operator<< is more idiomatic. It just takes ostream as parameter and makes the conversion implicit when streaming, but since this is idiomatic in C++, most will expect an overloaded operator<< when seeing std::cout << x;.

Samaveda answered 3/12, 2014 at 10:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.