2014-04-04 23 views
2

所以,我今天早些時候正在用C++搞亂,特別是鏈式插入操作符。我注意到了一些對我來說很奇怪的東西。鏈接插入運算符的任何其他陷阱?

#include <iostream> 
using namespace std; 

size_t& foo(size_t& n) { 
    ++n; 
    return n; 
} 

int main() { 
    size_t bar = 5; 
    cout << bar << " a " << foo(bar) << " b " << bar; 
    cin >> bar; //Ignore this, it's only here as an easy way to keep the window open 
} 

運行這個,而不是讓5 a 6 b 6實際上給6 a 6 b 5。顯然,插入操作數是從右到左計算的,但是從左到右打印,這可以解釋爲什麼更新後的值出現在函數調用之前,以及原始值之後。

當然,這可以通過簡單地有cout foo(bar)自己的行來解決,但我離題了。

是否還有其他奇怪的事情,我應該在鏈接插入運算符時意識到這一點?另外,有沒有人知道爲什麼插入操作符做到這一點?

回答

5

您正在讀取bar兩次,併爲函數調用引用一次,從而增加它。該函數調用相對於讀取而言被不確定地排序,這些讀取彼此不相關,因此任何排序都是允許的,並且它是未指定的,您將得到。

你不完全有未定義的行爲(一切都會發生),但未指定的行爲沒有太多樂趣。這是四個有效輸出:

5 a 6 b 5 
5 a 6 b 6 
6 a 6 b 5 
6 a 6 b 6 
+0

啊,謝謝。我從來沒有聽說過 - 我不知道這實際上是一個問題。我最熟悉的語言是Python,我不記得這是一個問題。 – Kevin

+0

是嗎? (cout.insert(a))。insert(b(a )))。insert(a)'和圓括號是序列點,不是嗎? (並且我運行他的程序並獲得了5 6 6 ...) – Massa

+0

實際上,他的例子中唯一的順序點是:在對函數的所有參數進行評估之後,但在函數執行之前。繪製自己的圖形,你會發現讀取和寫入可以同時發生。所以,沒有足夠的序列點,特別是兩次讀取和一次寫入條之間沒有。 – Deduplicator

3

的 「鏈式插入運營商」 只是函數調用。 C++在該語句中有幾條有關函數調用的簡單規則:

  1. 單個語句中的函數調用不會交錯 - 在下一個語句開始之前完成一次。 (即在語句中沒有多線程)
  2. 當一個函數調用的返回值是另一個函數的參數時,第一個函數調用必須在第二個函數調用之前發生。

沒有規則說明單個函數的多個參數必須在調用之前或緊接在調用之前進行評估 - 這組排序規則非常短。

現在我們在鏈中看到的是,不同的operator<<調用實際上是鏈接的:第二個調用使用第一個調用的返回值。這就是爲什麼這些運營商都返回std::ostream&。因此,您的輸出以正確的順序出現。

foo(bar)由相同的邏輯必須被稱爲之前其返回值被打印。但可以在任何以前的時間調用它。回想一下,沒有規則必須同時評估單個函數的2個參數。第三個operator<<調用的兩個參數是foo(bar)的返回值和第二個operator<<的返回值。所以,當第三個值被打印時,兩者都必須發生。因此a之後打印的數字是6。

如果您聲明結果應該是5 a 6 b 6,則假定第五個調用的第二個參數在第三個調用的第二個參數之後被評估。根本沒有關於這個的規則。