GotW #6a: 正しい const 第1部(勝手訳)

GotW #6a の翻訳です。

例によって原文著者(Herb Sutter 氏)の許可は得ていませんし、私の英訳がヒドいクオリティである(用語の統一がとれていないとか、誤訳が含まれているとか)かもしれませんのでそこのところはご理解いただければと思います


原文:http://herbsutter.com/2013/05/24/gotw-6a-const-correctness-part-1-3/

const and mutable have been in C++ for many years. How well do you know what they mean today?

const と mutable は長年にわたって C++ に存在しています。現在それらがどういう意味を持つか、あなたはどのくらい知っていますか?

Problem 問題

JG Question 見習いグルへの問題

1. What is a “shared variable”?

1. "共有変数" とは何か?

Guru Questions グルへの問題

2. What do const and mutable mean on shared variables?

2. 共有変数に対する const と mutable は何を意味するか?

3.How are const and mutable different in C++98 and C++11?

3. const と mutable は C++98 と C++11 においてどのように異なるか?

Solution 解答

1. What is a “shared variable”?

1. "共有変数" とは何か?


A “shared variable” is one that could be accessed from more than one thread at the same time.

"共有変数" は複数のスレッドから同時にアクセスできる変数のことです。


This concept is important in the C++ memory model. For example, the C++ memory model (the core of which is described in ISO C++ §1.10) prohibits the invention of a write to a “potentially shared memory location” that would not have been written to in a sequentially consistent execution of the program, and the C++ standard library refers to this section when it prohibits “modify[ing] objects accessible by [other] threads” through a const function, as we will see in #2.

このコンセプトは C++ メモリモデルにおいて重要です。たとえば、C++ メモリモデル(核となる部分は ISO C++ §1.10 に記述されています)は”潜在的に共有されるメモリ位置" への書き込みを禁じていて、プログラムが正しく実行されている限りにおいてはそのような場所に書き込まれることはありません。また、C++ 標準ライブラリが次の 2. でこれから見るように const 関数によって "[他の] スレッドからアクセス可能なオブジェクトの変更" を禁じているのは、このセクションを参照しているためです。

2. What do const and mutable mean on shared variables?

2. 共有変数に対する const と mutable は何を意味するか?

Starting with C++11, const on a variable that is possibly shared means “read-only or as good as read-only” for the purposes of concurrency. Concurrent const operations on the same object are required to be safe without the calling code doing external synchronization.

C++11 の場合から見て行きましょう。共有される可能性のある変数に対する const は、並列実行の目的のために "読み取り専用 もしくは 読み取り専用と同等" を意味します。同じオブジェクトに対する並列 const 操作は外部的に同期をするためのコードの呼び出しなしで安全であるために必要とされます。

If you are implementing a type, unless you know objects of the type can never be shared (which is generally impossible), this means that each of your const member functions must be either:

あなたが何かの型を実装しようとしているなら、その型のオブジェクトが絶対に共有されることがないとわかっている場合(そんなことは普通ありませんが)を除いて、すべての const メンバ関数は以下のいずれかである必要があることを意味します:

  • truly physically/bitwise const with respect to this object, meaning that they perform no writes to the object’s data; or else
  • internally synchronized so that if it does perform any actual writes to the object’s data, that data is correctly protected with a mutex or equivalent (or if appropriate are atomic<>) so that any possible concurrent const accesses by multiple callers can’t tell the difference.
  • このオブジェクトに関して、真に物理的にビット単位で const である。すなわち、そのオブジェクトのデータに対して書き込みが行われない;もしくは、
  • 内部的に同期しているので実際にはそのオブジェクトのデータに何か書き込んでいるが、そのデータは mutex もしくは同等のもの(もしくは atomic<> が適切ならそれ)を用いて正しく保護されているので、複数の呼び出し元からの何らかの並列な const アクセスにはそれがわからないようになっている。

Types that do not respect this cannot be used with the standard library, which requires that:

これを守らない型は、標準ライブラリと組み合わせて使うことができません。標準ライブラリは以下のことを要求しています:

“… to prevent data races (1.10). … [a] C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const arguments, including this.”—ISO C++ §17.6.5.9

"... データの競合 (1.10) を防ぐため ... [a] C++ 標準ライブラリ関数は、関数の非 const な引数(this を含む)によって直接または間接的にアクセスされる以外は、直接または間接的に現在のスレッド以外のスレッドからアクセスされるオブジェクト(1.10) を変更してはならない。- ISO C++ §17.6.5.9

Similarly, writing mutable on a member variable means what it has always meant: The variable is “writable but logically const.” Note what this implies:

同様に、メンバ変数に mutable と書くことはすでに述べたような意味を持ちます:その変数は "書き込み可能だが論理的には const" です。これが意味するところは、

  • The “logically const” part now means “can be used safely by multiple concurrent const operations.”
  • The “mutable” and “writable” part further means that some const operations may actually be writers of the shared variable, which means it’s inherently got to be correct to read and write concurrently, so it should be protected with a mutex or similar, or made atomic<>.
  • "論理的に const" の部分は "複数の並列 const 操作によって安全に使うことができる" ということを意味します。
  • "mutable" と "書き込み可能" の部分は、const 操作 によって実際には共有変数に書き込むことがあるかもしれないということを意味し、read と write が同時に行われたとしても本質的に正しくなければならないということであり、mutex もしくは同等のもの、もしくは atomic<> にすることによって保護されるべきであるということです。


In general, remember:

一般的には、以下のルールを覚えてください:

Guideline: Remember the “M&M rule”: For a member variable, mutable and mutex (or atomic) go together.

ガイドライン:"M&M ルール" を覚えましょう:メンバ変数に対して、mutable と mutex (もしくは atomic)はいつも一緒に使います。


This applies in both directions, to wit:

これは両方の面から適用されます。すなわち:

(1) For a member variable, mutable implies mutex (or equivalent): A mutable member variable is presumed to be a mutable shared variable and so must be synchronized internally—protected with a mutex, made atomic, or similar.

(1) メンバ変数に対して、mutable は mutex(もしくは同等のもの)を示唆します:mutable なメンバ変数は mutable な共有変数であると推測されるので、内部的に同期されなければなりません。すなわち mutex を使うかもしくは atomic にするかもしくは同等の方法で保護されなければなりません。

(2) For a member variable, mutex (or similar synchronization type) implies mutable: A member variable that is itself of a synchronization type, such as a mutex or a condition variable, naturally wants to be mutable, because you will want to use it in a non-const way (e.g., take a std::lock_guard) inside concurrent const member functions.

(2) メンバ変数に対して、mutex(もしくは同等の同期型)は mutable を示唆します:mutex や条件変数のような同期型のメンバ変数は、必然的に mutable になるはずです。非 const な方法(たとえば std::lock_guard を取るとか)で並列 const メンバ関数の中から使われるからです。


We’ll see an example of (2) in Part 2, GotW #6b.

(2) の例は第2部、GotW #6b で見て行きましょう。

3. How are const and mutable different in C++98 and C++11?

3. const と mutable は C++98 と C++11 においてどのように異なるか?

First, let’s be clear: C++98 single-threaded code still works. C++11 has excellent C++98 compatibility, and even though the meaning of const has evolved, C++98 single-threaded code that uses the old “logically const” meaning of const is still valid.

まず一つ明確にしておきましょう:C++98 のシングルスレッドのコードは今でも動きます。C++11 は C++98 との素晴らしい互換性を持っていて、const の意味が改良されたにもかかわらず、const をその古い "論理的に const" の意味で使っている C++98 のシングルスレッドのコードは今でも有効です。

With C++98, we taught a generation of C++ developers that “const means logically const, not physically/bitwise const.” That is, in C++98 we taught that const meant only that the observable state of the object (say, via its non-private member functions) should not change as far as the caller could tell, but its internal bits might change in order to update counters and instrumentation and other data not accessible via the type’s public or protected interface.

C++98 では、私たちは C++ 開発者達に "const というのは論理的に const であって物理的/ビット単位で const ということではない" と教えました。すなわち、C++98 においては、cost はそのオブジェクトの観測可能な状態(つまり、private でないメンバ関数を介して、ということですが)が呼び出し元にわかる範囲では変わってはいけないが、内部のビットはカウンタや計測値を更新したり、public もしくは protected なインターフェースを介してアクセスされることのないようなデータは変化しても良いと教えました。

That definition is not sufficient for concurrency. With C++11 and onward, which now includes a concurrency memory model and thread safety specification for the standard library, this is now much simpler: const now really does mean “read-only, or safe to read concurrently”—either truly physically/bitwise const, or internally synchronized so that any actual writes are synchronized with any possible concurrent const accesses so the callers can’t tell the difference.

この定義は並列性のためには不十分です。C++ およびそれ以降では、標準ライブラリのための並列性メモリモデルとスレッド安全性仕様が含まれます。これはよりシンプルです:const は "読み取り専用、もしくは並列読み取りに対し安全" という意味を持つようになりました。これは、真に物理的にビット単位で const であるか、実際には書き込みが同期されていても並列 const アクセス時に呼び出し元に違いがわからないようにするために内部的に同期するかのどちらかです。

Although existing C++98-era types still work just fine in C++98-era single-threaded code for compatibility, those types and any new ones you write today should obey the new stricter requirement if they could be used on multiple threads. The good news is that most existing types already followed that rule, and code that relies on casting away const and/or using mutable data members in single-threaded code has already been generally questionable and relatively rare.

既存の C++98 時代の型は互換性のために今でもちゃんと C++98 時代のシングルスレッドコードで動きます。そのような型も、あなたが今日以降書く新しい型も、もしマルチスレッドで使われるのであれば、新しい厳格な方の要求に従うべきです。良いニュースはほとんどの既存の型は実はそのルールにすでに従っているということです。また、シングルスレッドのコードにおいて、キャストによって cosnt を外してしまうことや mutable なデータメンバを使うことに依存しているようなコードは一般的にはいかがわしいものとされてきましたし、相対的にレアなものです。

Summary まとめ

Don’t shoot yourself (or your fellow programmers) in the foot. Write const-correct code.

自分自身の足を撃ちぬかないでください(もしくは同僚プログラマの足を)。正しい const のコードを書いてください。

Using const consistently is simply necessary for correctly-synchronized code. That by itself is ample reason to be consistently const-correct, but there’s more: It lets you document interfaces and invariants far more effectively than any mere /* I promise not to change this */ comment can accomplish. It’s a powerful part of “design by contract.” It helps the compiler to stop you from accidentally writing bad code. It can even help the compiler generate tighter, faster, smaller code. That being the case, there’s no reason why you shouldn’t use it as much as possible, and every reason why you should.

一貫して const を使うことは正しく同期されるコードのために必要です。それだけでも、一貫して "正しい const" であるべき理由として十分ですが、理由はほかにもあります:正しい const は、インターフェースと不変性について /* これは変更しないよ。約束します! */ なんて書くよりもずっと効率的にドキュメンテーションすることにもなります。これは ”契約によるデザイン (design by contract)" のパワフルな部分です。

Remember that the correct use of mutable is a key part of const-correctness. If your class contains a member that could change even for const objects and operations, make that member mutable and protect it with a mutex or make it atomic. That way, you will be able to write your class’ const member functions easily and correctly, and users of your class will be able to correctly create and use const and non-const objects of your class’ type.

mutable を正しく使うことが "正しい const" のキーになることを覚えておいてください。もしあなたのクラスが const オブジェクトに対する const 操作であっても変更されるようなメンバを持っているなら、そのメンバを mutable にしてそれを mutex で守るか atomic にしてください。そうすると、あなたのクラスの const メンバ関数は簡単に正しく書くことができるようになりますし、あなたのクラスを使う人もあなたのクラス型の const もしくは 非 const オブジェクトを正しく生成して使うことができるようになります。

It’s true that not all commercial libraries’ interfaces are const-correct. That isn’t an excuse for you to write const-incorrect code, though. It is, however, one of the few good excuses to write const_cast, plus a detailed comment nearby grumbling about the library vendor’s laziness and how you’re looking for a replacement product.

すべての商用ライブラリのインターフェースが "正しい const" ではないことは確かです。しかしそれはあなたが "正しくない const" のコードを書くための言い訳にはなりません。ただし、それは const_cast を書くための、そしてそのライブラリのベンダの怠慢やあなたが代わりになる製品を探していることについて愚痴をこぼすための詳細なコメントをそのライブラリのコードの近くに書くための言い訳として許される、数少ないものの一つです。

Acknowledgments 謝辞

Thanks in particular to the following for their feedback to improve this article: mttpd, jlehrer, Chris Vine.

この記事をより良くするための以下の各位からのフィードバックに対し、特に感謝する:mttpd, jlehrer, Chris Vine.