C++使用toString()方法有什么问题?

8

我刚刚看到了这个问题,它是关于如何通过 ToString() 方法打印对象的。

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

据我所知,实现此功能的标准方式是重载ostream的<<运算符。然而,这样做是在ostream上添加一个特性,而不是在我的类上添加一个特性。

另一种选择(也作为上述问题的答案给出)是覆盖字符串转换运算符。然而,这会导致“意外转换和难以跟踪的错误”的警告。

现在我想知道编写toString()方法并通过它使用是否有任何缺点。

std::cout << x.toString() << std::endl;

你是指如何将 int 转换吗?还是你的意思是使用 to_string 函数? - doctorlove
因为使某些东西可流式并不等同于将其转换为字符串。如果您想让您的类型可流式,请重载 ostream& operator<<。如果您想将其转换为字符串,请给它一个 to_string 成员。 - juanchopanza
5个回答

5
输出流不仅处理输出,还处理输出格式。因此,使用toString()方法时,客户端将无法像处理其他内容一样管理对象的格式:
// 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"

也许您不介意不允许任何格式,所以也许这并不重要。
另一个考虑因素是输出到流不需要一次性将整个字符串表示在内存中,但您的toString()方法需要。
其他人已经指出了这一点,但我认为更清晰的表达方式是,你的类接口不仅限于它提供的方法,还包括你构建的其他函数,包括非成员函数,例如你提供的operator<<重载。即使它不是你的类的方法,你仍然应该将其视为你的类的一部分接口。
这里有一篇文章讨论了这个问题,也许你会觉得有帮助:How Non-Member Functions Improve Encapsulation
这是一个关于为用户定义的类重载operator<<的简单示例:
#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';
}

抽象类怎么办?我可以有一个toString()=0;但是如何声明任何子类都应该带有重载的<<运算符? - 463035818_is_not_a_number
或许我应该先阅读你发的链接 ;) 第一个点说了:“如果(f需要是虚函数),就把f变成成员函数”。 - 463035818_is_not_a_number
@tobi303 目前来看,虚函数必须是成员函数。未来非成员函数可能会获得虚分派的支持:http://www.stroustrup.com/multimethods.pdf - bames53

4
据我所了解,实现此功能的标准方法是重载ostream << 操作符。但是,这将向ostream添加一个特性,而不是向我的类添加一个特性。
这是个好事情。你的类越小越好。如果流行的C++惯用语能让你从类中获得更多东西,为什么不遵循它呢?
现在,我想知道编写toString()方法是否有任何缺点。
缺点如下:
- operator<< 以统一的方式与内置类型(例如int)和用户定义的类型一起工作。toString只适用于类。 - C++比Java更异构。std::string是最受欢迎的字符串,但仍存在其他字符串类,并且也在使用中。 - 必须创建字符串,可能会导致性能损失。如果直接写入流,可以避免这种情况。

这些缺点远不及使用统一序列化方法封装在任何类中所带来的优点重要。为什么我们要关心不使用std::string等不良实践呢? - Gerd Wagner

3
他们是根本不同的事情。提供一个 operator<< 重载实际上扩展了流的接口,使您的类类型对象可流式传输。提供一个 toString 函数扩展了您的类的接口,使得从您的类中获取 std::string 成为可能。它们代表不同的东西。
您的类的接口应该完全对应于它在程序逻辑中所表示的内容(单一职责)。很少有 toString 是一个类接口的自然部分。然而,扩展流的接口以接受更多对象则更加合理。
也就是说,在一个情况下,您是在说“现在可以流式传输这个类类型的对象”。在另一个情况下,您是在说“您可以将这个类类型的对象转换成一个 std::string 。”—— 只是这个 std::string 然后是可流式传输的。想一想——我的Person 类是否真的有一个 toString 函数是有意义的?我什么时候能将人变成文本了呢?

4
我认为这是否合理是个人品味的问题。在我看来,任何Java对象通过toString()方法都可以被转换为字符串,这完全是有意义的。 - 463035818_is_not_a_number

1
你的第一个假设是错误的。你不需要在ostream中进行任何更改。
像 operator<< 这样的运算符方法可以通过两种方式定义:作为 ostream 类的方法,以您的 x 对象作为参数,或作为带有两个参数的普通旧函数,将 ostream 作为第一个参数,并将您的 x 对象作为第二个参数。

0

每个类都有一个toString()函数并没有什么问题。它具有更明确的优点,可以实现多态性,并且可以在流之外的其他情况下使用,但是在团队合作时需要有编码规则(“是to_string()、str()还是streaming()?”)。

重载operator<<更符合惯用法。它只需要将ostream作为参数,并在流式传输时进行隐式转换,但由于这在C++中是惯用法,大多数人在看到std::cout << x;时会期望重载的operator<<。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接