コンテンツへスキップ
Topos Uranos

要約

C++ の式は二つの異なることを行いうる。値を与えることと、プログラムの状態を変えることである。x + 1 は値を与えるだけである。x = 7 は値を与え、さらに x を変える。x++ は x がもっていた値を与え、x には別の値を残す。このレッスンは、式の値と作用のこの違いを学ぶ。式としての代入、複合代入、前置形と後置形のインクリメントとデクリメント、そして式の各部分が計算される順序であり、この順序を規格は一部しか定めていない。ここから、レッスンの残りを貫く問いが生じる。規格はプログラムについて何を約束するのか。その答えは、定義された動作から未定義の動作までの四段階からなる一覧であり、これまでに見たすべての場合をまとめ、それぞれについて、何がそれを検出するか(コンパイラ、サニタイザ、ライブラリ)、そして何がだれにも検出されないかを示す。

学習目標

  1. 代入、インクリメント、関数呼び出しにおいて、式の値とその副作用を区別する。
  2. 値として使われた x++ と ++x の結果を予測し、複合代入を含むプログラムを手で追う。
  3. 規格が順序を定めないまま、一つの変数を二度変更する式、あるいは読みかつ変更する式を見分け、別々の文に書き直す。
  4. ある構文を、定義された動作、処理系定義、未規定、未定義のいずれかに分類し、それぞれの種類について例を一つ挙げる。
  5. 一覧にある未定義動作のそれぞれについて、コンパイラが警告するか、サニタイザやライブラリが止めるか、それともどれも検出しないかを述べる。

値と作用

レッスン 算術演算 は、式を一つの値に簡約される木として学んだ。2 + 3 * 4 の値は 14 であり、それを計算しても式そのものの外は何も変わらない。レッスン 関係と論理 は、値が true か false である条件について同じことをした。しかし、値を与えるだけでなく、プログラムの状態に痕跡を残す式がある。変数の値を変える、画面に書く、入力から読む、といった式である。この痕跡を副作用、あるいは単に作用という。「副」という字は重要度が低いことを意味しない。x = 7 では、作用、つまり x の値が 7 になることこそが目的である。

そうであれば、どの式も二つの問いをもって読むべきである。何を与えるか、そして何をするか。三つの例でこの区別を確かめよう。count + 1 は count の次の数を与え、何もしない。count は値を保つ。count = 9 は 9 を与え、count の値を 9 にする。std::println("こんにちは") もまた式であり、関数の呼び出しであって、そこで大事なのは作用、すなわち一行を書くことである。自分で書く関数はレッスン 関数 で学ぶが、そこでもこの区別がまた現れる。結果を計算するだけの関数は、そのうえ外部の何かを変える関数よりも、理解しやすく、テストしやすい。

count + 1; のような文は正しい C++ である。値は計算されて捨てられるので何の役にも立たず、GCC は -Wunused-value でそれを警告する。式文は、その作用によって意味をもつ。したがって、作用をもつ式こそがプログラムを動かす式であり、同時に注意を要する式でもある。作用はある時点で起こるが、その時点は読み手が想定する時点とは限らないからである。