【问题标题】:What bad things would happen if `std::ostringstream` had an `operator <<` which returns `std::ostringstream &`?如果 `std::ostringstream` 有一个返回 `std::ostringstream &` 的 `operator <<` 会发生什么坏事?
【发布时间】:2017-05-06 02:26:07
【问题描述】:

在某些语言(如 Ruby)中,有用于创建格式化输出的精美语法 作为“单线”,部分原因是 Ruby 应用程序需要这种方式的普遍性。

在一些 C++ 应用程序中也经常需要它。下面是一些示例代码:

if (num_doggies > num_biscuits) {
  std::ostringstream ss;
  ss << "There are " << num_doggies" << " puppy dogs and only " << num_biscuits << " biscuits!";
  log_warn(ss.str());
  this->negotiate_biscuit_reduction();
}

一些开源 C++98 项目的解决方法如下所示:

struct string_formatter {
  std::ostringstream ss;

  template <typename T>
  string_formatter & operator << (const T & t) {
    ss << t;
    return *this;
  }

  operator std::string() const {
    return ss.str();
  }
};

这样他们就可以写出这样的东西:

if (num_doggies > num_biscuits) {
  log_warn(string_formatter{} << "There are " << num_doggies" << " puppy dogs and only " << num_biscuits << " biscuits!");
  this->negotiate_biscuit_reduction();
}

将三行变成一行。

但是,从代码审查的角度来看,string_formatter 类看起来相当邪恶, 主要是因为这种隐式转换。通常考虑隐式转换 非常邪恶,当它们转换为普通或原始类型时,它们更加邪恶 喜欢intstd::string

在 C++11 中我们可以稍微改变一下:

  operator std::string() && {
    return ss.str();
  }

现在只有当string_formatter 是一个右值引用时才会触发转换。 所以它不太可能触发,但它也更复杂。它实际上是不那么邪恶吗? 值得商榷。

另外值得一提的是:其他人已经进行了更复杂的黑客攻击以使其正常工作 没有string_formatter 垫片:https://codereview.stackexchange.com/questions/6094/a-version-of-operator-that-returns-ostringstream-instead-of-ostream

这里没有隐式转换,但我们在标准库类型上重载运算符,这通常是禁忌。


让我们稍微备份一下。为什么我不能妥协并做到这一点?它至少更紧凑一点:

if (num_doggies > num_biscuits) {
  std::ostringstream ss;
  log_warn((ss << "There are " << num_doggies" << " puppy dogs and only " << num_biscuits << " biscuits!").str());
  this->negotiate_biscuit_reduction();
}

事实证明这不会编译,因为std::ostringstream::operator &lt;&lt; 返回std::ostream &amp; 而不是 std::ostringstream &amp;。当所有&lt;&lt; 都解决时,我们只有一个ostream &amp;,所以我们不能使用.str()

我的问题是,为什么会这样?

为什么std::ostringstream::operator &lt;&lt; 不返回std::ostringstream &amp;?是吗:

  • 出于技术或安全原因不受欢迎
  • 为标准库实现者增加不必要的困难
  • 旧版 - 一直都是这样,现在更改它可能会破坏代码。 (但是,这究竟会破坏什么代码?由于继承关系,std::ostringstream &amp; 可以通过标准转换转换为std::ostream &amp;?)
  • 默默无闻——没人关心!

注意:我很清楚设计string_formatter 和记录器类的不同方法。我特别感兴趣的是标准库中是否存在使std::ostringstream 返回std::ostream &amp; 更好的设计原因。

【问题讨论】:

  • 你可以简单地将.str() 添加到string_formatter,不是吗?
  • @T.C.是的,但这不是重点,如果有什么我不明白为什么std::ostringstream 需要返回std::ostream &amp; 的充分理由,我会更感兴趣
  • 因为ostringstream中没有operator
  • @ChrisBeck - 当然,可以为 every ostream 类型的 every 插入器提供包装函数。这也意味着用户编写的流必须为每个插入器提供包装器,并且用户编写的流插入器必须为每个 ostream 类型提供包装器。就个人而言,我宁愿只写一个可以在任何地方工作的插入器,而不是 N 个适用于所有风格的 ostream 的插入器,除了我忘记的那些。
  • 我想有人可以说ostream&amp;-taking, ostream&amp;-returning operator&lt;&lt;s 是自定义点,然后在顶部为具体流提供全局适配器,类似于operator&lt;&lt; 的工作方式对于右值流。这样的设计具有显着的额外复杂性,并且似乎不是很有用。 (os &lt;&lt; a &lt;&lt; b).stuff() 既不漂亮也不优雅;如果由于某种原因您必须将其压缩到一行中,带有逗号运算符的os &lt;&lt; a &lt;&lt; b, os.stuff() 可以正常工作,并且几乎一样丑陋。

标签: c++ c++11 stream c++-standard-library c++98


【解决方案1】:

如果您可以简化处理记录警告消息的代码,您是否会关心 operato&lt;&lt; 函数返回的内容。

这是一种简化记录警告消息的代码的方法。

#include <iostream>
#include <sstream>
#include <string>

void log_warn(std::string const& s)
{
   std::cout << s;
}

struct log_warning_impl
{
  mutable std::ostringstream ss;

  ~log_warning_impl()
  {
     log_warn(ss.str());
  }
};

template <typename T>
log_warning_impl const& operator<<(log_warning_impl const& l, const T & t)
{
   l.ss << t;
   return l;
}

log_warning_impl const& operator<<(log_warning_impl const& l, std::ostream&(*f)(std::ostream&))
{
   l.ss << f;
   return l;
}


#define WARNING_LOGGER log_warning_impl{}

int main()
{
   WARNING_LOGGER << "There is a problem in the " << 10 << "-th line of the data file" << std::endl;
}

在你的情况下,你可以简单地使用:

if (num_doggies > num_biscuits)
{
   WARNING_LOGGER << "There are " << num_doggies << " puppy dogs and only " << num_biscuits << " biscuits!";
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-11
    • 2013-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-03
    相关资源
    最近更新 更多