Uwaga z ciągami zapisów do strumienia

Ktoś się kiedyś dziwił, że fragment kodu

cout << obj.Incr() <<  " " << obj.Incr() << " " << obj.Incr() << " "
     << obj.Incr() << " " << obj.Incr() << " " << obj.Incr() << " "
     << obj.Incr() << " " << obj.Incr() << " " << obj.Incr() << " "
     << obj.Incr() << " " << obj.Incr() << endl;

(gdzie metoda Incr modyfikowała pewien atrybut klasy i zwracała opartą na nim wartość - w uproszczeniu można przyjąć że inkrementowała atrybut i zwracała ile on aktualnie wynosi) na jednych platformach wypisywał

0 1 2 3 4 5 6 7 8 9 10

a na innych

10 9 8 7 6 5 4 3 2 1

Wytłumaczenie jest proste: zapis

  cout << obj.Incr();

jest tak naprawdę wywołaniem funkcji

  operator<<(cout, obj.Incr())

zwracającej ostream&. W szczególności

  cout << obj.Incr() << obj.Incr;

to tak naprawdę

  operator<<( operator<<(cout, a.Incr() ), a.Incr() );

Kompilator ma swobodę przy ustalaniu kolejności obliczania parametrów w takim wyrażeniu. Niektóre obliczą podwyrażenia "od lewej", inne "od prawej" (poprawny byłby nawet kompilator, który kolejność obliczania podwyrażeń by sobie za każdym razem losował).

Wniosek? Trzeba uważać, gdy wpisujemy do strumieni wyniki funkcji posiadających efekty uboczne - i raczej nie stosować wówczas zapisu "łańcuchowego" tylko coś w stylu

  cout << obj.Incr();
  cout << obj.Incr();
  // ...
komentarze obsługiwane przez Disqus