ostream chaining,输出顺序

11

我有一个函数,它接受一个 ostream 引用作为参数,将一些数据写入流中,然后返回对同一流的引用,代码如下:

#include <iostream>

std::ostream& print( std::ostream& os ) {
  os << " How are you?" << std::endl;
  return os;
}

int main() {
  std::cout << "Hello, world!" << print( std::cout ) << std::endl;
}

这段代码的输出是:

 How are you?
Hello, world!0x601288

然而,如果我将链接表达式拆分为两个语句,像这样:

int main() {
  std::cout << "Hello, world!";
  std::cout << print( std::cout ) << std::endl;
}

那么我至少可以在输出中得到正确的顺序,但仍会得到一个十六进制值:

Hello, world! How are you?
0x600ec8

我想了解这里正在发生什么。普通函数是否优先于 operator<<,这就是输出顺序反转的原因?编写一个可以将数据插入到 ostream 中并且可以与 operator<< 链接的正确方式是什么?

5个回答

6

根据C++标准,您的代码行为是未指定的。

解释

以下内容(为了简单起见,我删除了std::endl

std::cout << "Hello, world!" << print( std::cout ); 

等同于这个:

operator<<(operator<<(std::cout, "Hello, World!"), print(std::cout));

这是一个函数调用,传递了两个参数:

  • 第一个参数是:operator<<(std::cout, "Hello, World!")
  • 第二个参数是:print(std::cout)

现在,标准没有规定参数的评估顺序。这是未指定的。但是你的编译器似乎先评估第二个参数,这就解释了为什么它首先打印出“你好吗?”评估第二个参数得到std::ostream&类型的值,然后将该值传递给上述调用(该值是对象std::cout本身)。

为什么输出是十六进制?

因为第二个参数评估为std::cout,而std::cout被打印成十六进制数字,是因为std::cout隐式转换为void*类型的指针值,所以它被打印成十六进制数字。

试试这个:

void const *pointer = std::cout; //implicitly converts into pointer type!
std::cout << std::cout << std::endl;
std::cout << pointer << std::endl;

它将打印相同的值。例如,这个在ideone上的例子会打印出这个结果:
0x804a044
0x804a044 

请注意,我没有使用显式转换; 相反,std::cout会被隐式转换为指针类型。
希望这有所帮助。

编写一个函数以向ostream中插入数据,但该函数也可以与operator<<链接。如何正确地实现?

当你说“链接”时,这取决于你的意思? 显然,以下内容不起作用(如上面所述):
std::cout << X << print(std::cout) << Y << Z; //unspecified behaviour!

无论您如何编写print(),它都是明确定义的。
但是下面这个更为明确:
print(std::cout) << X << Y << Z; //well-defined behaviour!

1
不回答“编写一个函数将数据插入到ostream中的正确方法是什么,但也可以与'operator<<'链接?” - Ben Voigt
@Mr.Anubis:我正在吃晚餐中:P - Nawaz

3

十六进制输出

在C++11之前,类 std::ostream 有一个转换函数可以将它转换为 void*。由于你的 print 函数返回 std::ostream&,当求值 std::cout << print(...) 时,返回的 std::ostream 左值会被隐式转换为 void*,然后作为指针值输出。这就是为什么会出现十六进制输出的原因。

从C++11开始,这个转换函数被替换为一个 显式 转换函数,用于将 std::ostream 对象转换为 bool,因此尝试输出 std::ostream 对象将会导致编译错误。

求值顺序

在C++17之前,重载运算符在分析求值顺序时被视为函数调用,不同参数的求值顺序是未指定的。因此,先评估 print 函数,这导致 How are you? 首先被输出,这并不奇怪。

从C++17开始,重载运算符 << 中的操作数求值顺序严格从左到右,并且重载运算符的操作数共享内置运算符的求值顺序(更多细节请参见这里)。因此,你的程序将始终得到输出(假设 print 返回某种可输出的值)。

Hello, world! How are you?
something returned by print

LIVE EXAMPLE


3

原因是 print() 函数将在语句的其余部分之前进行评估,并返回对 cout 的引用,然后作为指针实际打印出来 (cout << cout)。这种评估顺序实际上是未指定的行为,但似乎是您的编译器的情况。

至于定义一个流感知的“函数”,其实际具有相同功能的已定义行为,则会起作用;

#include <iostream>

template <class charT, class traits>
  std::basic_ostream<charT,traits>& print ( std::basic_ostream<charT,traits>& os )
{
        os << " How are you?" << std::endl;
        return os;
}

int main() {
  std::cout << "Hello, world!" << print << std::endl;
}

另请参阅此答案,以获取关于在这种情况下"未指定"实际含义的更多细节。


1
在你的语句中 std::cout << "Hello, world!" << print( std::cout ) << std::endl 中,std::cout << "Hello, world!"print( std::cout ) 的先后顺序是未定义的。这就是为什么输出的顺序可能不是你所期望的原因。
十六进制值来自于你同时执行了 std::cout << std::coutprint 返回的是被传递到 << 链中的 std::cout)。右侧的 std::cout 被转换成了一个 void * 并被打印到了输出中。

1

这个方法可行,可以结合print<<来控制顺序:

print( std::cout << "Hello, world!" ) << std::endl;

或者,如果你想要一个被称为 << 的函数,请参考 Joachim 的回答。


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