返回对此的引用时出现意外的std :: ostream输出

时间:2013-12-09 22:00:24

标签: c++ c++11 ostream

我显然必须遗漏一些非常基本的东西。

我有一个简单的类,我执行“就地”操作,然后返回对this的引用(这允许我链接不同的操作)。当我打印到std::ostream时,我看到一个意外的输出。以下代码将帮助我解释。

#include<iostream>

struct Container {

  Container(int x, int y, int z)
      : m_data{x, y, z} { }

  int operator()(size_t index) const {
    return m_data[index];
  }

  Container& shift() {
    int tmp = m_data[0];
    m_data[0] = m_data[1];
    m_data[1] = m_data[2];
    m_data[2] = tmp;
    return *this;
  }

  int m_data[3];
};

std::ostream& operator<<(std::ostream& os, const Container& c) {
  os<<"["<<c(0)<<", "<<c(1)<<", "<<c(2)<<"]";
  return os;  
}


int main() {
  std::cout<<std::endl<<"Behaviour Line-By-Line (works as expected)"<<std::endl;

  Container d(1, 2, 3);

  std::cout<<"Container as built:               "<<d<<std::endl;

  d.shift();  
  std::cout<<"Container after first shift:      "<<d<<std::endl;

  d.shift();
  std::cout<<"Container after second shift:     "<<d<<std::endl;

  std::cout<<std::endl<<"Behaviour On The Same Line (not as expected)"<<std::endl;

  Container c(1, 2, 3);

  std::cout<<"Container as built:               "<<c<<std::endl
           <<"Container after first shift:      "<<c.shift()<<std::endl
           <<"Container after second shift:     "<<c.shift()<<std::endl;


  return 0;
}

使用GCC 4.8.1编译(OS X 10.7.4)并运行:

$ g++ example.cpp -std=c++11 -Wall -Wextra
$ ./a.out

Behaviour Line-By-Line (works as expected)
Container as built:               [1, 2, 3]
Container after first shift:      [2, 3, 1]
Container after second shift:     [3, 1, 2]

Behaviour On The Same Line (not as expected)
Container as built:               [3, 1, 2]
Container after first shift:      [3, 1, 2]
Container after second shift:     [3, 1, 2]

正如您所看到的,当我将修改操作放在与operator<<相同的行上时,输出似乎是缓冲区(因为没有更好的单词)更改。

我的问题是:

为什么会发生这种情况,如何使“On The Same Line”行为与“Line-By-Line行为”相匹配。

谢谢!

编辑:

根据@ KeithSmith的建议,我将Container::shift修改为:

  Container& shift() {
    std::cout<<"shifting... "<<std::flush;
    int tmp = m_data[0];
    m_data[0] = m_data[1];
    m_data[1] = m_data[2];
    m_data[2] = tmp;
    return *this;
  }

得到了输出:

Behaviour Line-By-Line (works as expected)
Container as built:               [1, 2, 3]
shifting... Container after first shift:      [2, 3, 1]
shifting... Container after second shift:     [3, 1, 2]

Behaviour On The Same Line (not as expected)
shifting... shifting... Container as built:               [3, 1, 2]
Container after first shift:      [3, 1, 2]
Container after second shift:     [3, 1, 2]

如几个答案所述,未定义操作的顺序。在“不尽如人意”的情况下,转换发生在流式传输之前,但我想它可能以任何顺序发生。

我的外卖:inlining有副作用的手术时非常非常小心!我想我应该知道这个!可悲的是我没有!

2 个答案:

答案 0 :(得分:2)

未指定评估函数参数的顺序。如果你的表达有副作用,并且你依赖于他们的顺序,你需要确保按顺序执行。即,陈述

std::cout<<"Container as built:               "<<c<<std::endl
         <<"Container after first shift:      "<<c.shift()<<std::endl
         <<"Container after second shift:     "<<c.shift()<<std::endl;

可以作为

处理
  • 首先评估其中一个c.shift()
  • 第二次评估其他c.shift()
  • 现在它已评估所有对象并开始输出它们。

它也可以以不同的顺序评估表达式。只是移位运算符的函数调用是有序的,显然,它取决于子表达式,需要首先评估子表达式。

答案 1 :(得分:1)

第二个cout大致翻译为operator <<(operator <<(operator <<(cout, c), c.shift(), c.shift()),由于参数评估顺序未指定,所有的转换都可以在开头发生。

第一个;中的cout引入了序列点,确保了评估顺序。参见例如这个:Undefined behavior and sequence points了解有关序列点的更多信息。