「プログラミング言語C++第四版」について気が付いたことなど (19)

ロックフリープログラミングにはトライするなと言ってるのに …


41.3  アトミック性

…(略)…

単純なアトミックカウンタは例外だが、ロックフリープログラミングは、専門家のためのものだ。言語の仕組みを理解するだけではなく、特定のマシンアーキテクチャの詳細な把握と、専門の実装技法の知識が欠かせない。本書で提示する内容だけに基づいて、ロックフリープログラミングを試みてはいけない。ロック技法を把握した上で論理的に高度なロックフリー技法を活用すれば、デッドロックやスタベイションなどの古典的なロック問題は発生しない。


原文:

…. Do not try lock-free programming with only the information provided here. The primary logical advantage of lock-free techniques over lock-based techniques is that classical locking problems, such as deadlock and starvation, cannot happen.


試訳:

… 本書で提供されている情報だけでロックフリープログラミングを試みないように。ロックベースの技法に対するロックフリー技法の主要な理論的優位性は、デッドロックやスターベイションのような古典的なロッキングの問題が発生し得ないということ(だけ)だ。


考察:

生半可な知識でトライするなと言ってるくせに、その直後にすばらしい技法だと勧めているような物言いは不自然に思われます。「論理的に高度なロックフリー技術」という捉え方は「logical advantage of lock-free techniques」の訳としては間違っていますし(それを言うなら logically advanced lock-free techniques ?)、それを「活用すれば」とか、どこにも書いてないし。原文で言ってるのはそういうことではないです、たぶん。

「プログラミング言語C++第四版」について気が付いたことなど (18)

そんな大げさに言わなくても … (オマケ付き)


38.4.5.3 ユーザ定義の操作子

….(略)….

さて、この宣言によって、<<と()の組合せが、単一の3項演算子に統合されることに注意しよう。cout<<sci4{d}は、実際の処理を実行する前に、ostreamと書式と値を、単一の関数に集約する

38.5  ストリーム反復子


原文:

Note that these declarations make the combination of << and () into a ternary operator;
cout<<sci4{d} collects the ostream, the format, and the value into a single function before doing any real computation.


試訳:

これらの宣言が << と () の組み合わせを三項演算子にすることに注意しよう。cout<<sci4{d} は、実際の処理を実行する前に、ostreamと書式と値を単一の関数(オブジェクト)にまとめる


考察:

Form sci4; という宣言は見当たらないけどどこかにあるとして(名前から見て sci4.scientific().precision(4) も実行されているだろう)、sci4のForm::operator()を引数dと共に呼び出す(sci4{d})ことで新たに値dと結び付けられたBound_form型のインスタンスを作り出し、それを cout::operator<<(ostream&,const Bound_form&) に渡す、という流れ。

sci4{d} は sci4(d) と書いても同じ。

Form sci4;
Bound_form bf { sci4(d) };
cout << bf;

と分けて書いても同じ(dはどこかで宣言されているものとして)。

オマケの補足: なぜsci4{d} と書くことで Form::operator() が呼び出されるのか?
ぱっと見不思議な書き方で一瞬ミスプリントじゃないかと思ったくらいですが、これはどうも、{}の初期化リストからコンパイラがinitializer_list<>を自動生成するのと、作成されたinitialize_listの要素が一個の場合は引数一個のoperator()関数の呼び出しに変換されるようで、そのため、引数一個の場合に限りこのような書き方が出来るみたいです。(違うかも、実は自信ないです)
引数が無い場合や二個ある場合は{}を使った書き方はコンパイルエラーになってしまいます。operator()の呼び出しで汎用的に()の代わりに{}が使える訳ではない模様。

 

「プログラミング言語C++第四版」について気が付いたことなど (17)

向きが逆のような気がするのだけれども …


38. 4. 4   ストリーム の 状態

…(略)…

tie()は、あるストリームからの入力を行う前に、それに結び付いたストリームからの出力を先に行うことを保証するものだ。たとえば、coutにはcinが結び付けられている:


原文:

The tie() is used to ensure that output from a tied stream appears before an input from the stream to which it is tied. For example, cout is tied to cin:


考察:

cout に cinが結び付けられているのではなくて、cout は cinに結び付けられている、つまり、cin には coutが結び付けられている、ということ。

cinがcoutの情報を持っているから、cinからの入力の実行前にcoutの出力状態をチェックすることが出来るわけ。coutがcinの情報を持っていても cin側からcoutの状態を見ることは出来ない。

ちなみに、この後ろの方では “so had cout not been tied to cin” の訳として(正しく)「cout が cinに結び付けられていなければ」となっている。

……….

とは言うものの、これはちょっと細かいところにこだわり過ぎているかも知れない。というのは、C++ referenceサイトのstd::basic_ios::tieの説明 https://en.cppreference.com/w/cpp/io/basic_ios/tie には(cout is tied to cin and cerr ではなく)下記のように書かれているから。実は結び付きの向きなんて誰も気にしてないのかも 🙂

Notes

By default, the standard streams cin and cerr are tied to cout.

 

「プログラミング言語C++第四版」について気が付いたことなど (16)

どうでもいいんだけど、命名法の違いの理由が気になる …


33.4  関数オブジェクト

(算術演算向け関数オブジェクト)

f=plus<T>{}        xとyの型がTであれば、f(x,y) は x+y を意味する。
f=minus<T>{}       xとyの型がTであれば、f(x,y) は x-y を意味する。
f=multiplies<T>{}  xとyの型がTであれば、f(x,y) は x*y を意味する。
f=divides<T>{}     xとyの型がTであれば、f(x,y) は x/y を意味する。
f=modulus<T>{}     xとyの型がTであれば、f(x,y) は x%y を意味する。
f=negate<T>{}      xの型がTであれば、f(x) は -x を意味する。

考察:

multiplyとdivideだけに三単元もしくは複数形っぽいsが付いているのは何故?

一瞬、この二つは動詞だからかと思った(plus/minusは前置詞、modulusは名詞)けど、同じく動詞のnegateはnegatesにはなっていない … 。

関数オブジェクトのテンプレートの名前なので、名詞扱いにしているのだろうとは思うのだけど。negateの対象は一つだからsが付かないと考えるなら、multipliesとdividesのsは複数形のsと見ればいいのかしらん?

いずれにしてもこれはこの本ではなくてC++のライブラリの仕様の問題ですな。

 

「プログラミング言語C++第四版」について気が付いたことなど (15)

間違ってるわけではないのだけど、あらぬ誤解を招きそうな …


32.6.1   2分探索

…(略、(14)の続き)…

不思議に感じられるかもしれないが、2分探索アルゴリズムでは、ランダムアクセス反復子は不要であり、前進反復子で十分である。


原文:

Curiously enough, the binary search algorithms do not require random-access iterators: a forward iterator suffices.


考察:

forward iterator で実装できるのは確かなのだけれども、binary_search()は random-access iteratorが与えられた時はそれを使って探索を効率化することになっている。なので、「不要」と言い切っちゃうのは抵抗があるし、「前進反復子で十分」というのはあらぬ誤解を招くんじゃないだろうか。というわけで。


試訳:

奇妙に思えるかもしれないが、二分探索アルゴリズムにはランダムアクセス反復子は必須ではない。前進反復子でも用は足りる。

 

「プログラミング言語C++第四版」について気が付いたことなど (14)

微妙に怪しい訳 …


32.6.1   2分探索

…(略、(13)の続き)…

同様のエラー通知方法は、upper_ bound() や equal_ range() でも行われている。そのため、これらのアルゴリズムを用いると、ソートずみシーケンスに対して新規要素を挿入しても、ソートされた状態を維持できる。そのためには、返却されたpairのsecondの直前に新規要素を挿入する。


原文:

This way of reporting failure is also used by upper_bound() and equal_range(). This means that we can use these algorithms to determine where to insert a new element into a sorted sequence so that the sequence remains sorted: just insert before the second of the returned pair.


試訳:

同様のエラー通知方法はupper_bound()とequal_range()でも用いられている。このことは、ソート済み状態を維持したままでシーケンスに新しい要素を追加するにはどこに挿入すれば良いかを決めるためにこれらのアルゴリズムを利用できることを意味する。返却されたpairのsecondが指す要素の直前に挿入すればよい。


考察:

全体としての意味は合ってるんですけどね。ここで使われている so that は “~するために” の意味で訳したほうが分かり易いのではないかと。

「プログラミング言語C++第四版」について気が付いたことなど (13)

lower_boundとupper_bound、ややこしい仕様ではあるのだけれども …


32.6.1    2分探索

…(略)…

lower_bound(first,last,k)は、kを探索するのではなく、kより大きなキーをもつ先頭の要素を指す反復子を返す。ただし、kより大きなキーをもつ要素が存在しなければlastを返す。同様のエラー通知方法は、upper_bound()やequal_range()でも行われている。


原文:

If lower_bound(first,last,k) doesn’t find k, it returns an iterator to the first element with a key greater than k, or last if no such greater element exists. This way of reporting failure is also used by upper_bound() and equal_range().


試訳:

lower_bound(first,last,k)がkと一致するキーを見つけられない場合はkより大きなキーを持つ最初の要素を指す反復子を、その様な要素が存在しない場合はlastを返す。同様のエラー通知方法は、upper_bound() と equal_range() でも用いられている。


考察:

ややこしいが、(探索に成功した場合)lower_boundはkと一致する最初の要素を返し、upper_boundはkより大きなキーを持つ最初の要素を返す。なので「kを探索するのではなく」だとupper_boundの仕様になってしまう。たぶん、一覧表の方に出ているlower_bound関数の仕様(p=lower_bound(b,e,v)  pは、[b:e)内で最初に出現するvを指すようになる)を忘れていて、かつ文頭の”If”を見落としている。

 

「プログラミング言語C++第四版」について気が付いたことなど (12)

たぶん誤変換 …


20. 5. 2. 1   多重 継承 と アクセス 制御

格子上の多重継承において、基底クラスにたどり着く経路が複数個存在する場合(§21.3)、アクセス可能な経路があれば、基底クラスの名前が利用できる。たとえば:


原文:

If the name of a base class can be reached through multiple paths in a multiple-inheritance lattice (§21.3), it is accessible if it is accessible through any path. For example:


考察:

「格子状の多重継承」ではないかと思われ。

「プログラミング言語C++第四版」について気が付いたことなど (11)

仮想関数にだって何らかの用途はあります …


20.4 抽象クラス

…(略)… Shapeなどの一部のクラスは、そもそもオブジェクトが存在することのない抽象概念を表現する。Shapeは、そこから派生したクラスの基底となることに存在価値がある。このことは、何らかの用途をもったものとして仮想関数を定義できないという事実が示している


原文:

Some classes, such as a class Shape, represent abstract concepts for which objects cannot exist. A Shape makes sense only as the base of some class derived from it. This can be seen from the fact that it is not possible to provide sensible definitions for its virtual functions:


試訳:

Shapeなどの一部のクラスは、対象物が存在することのない抽象概念を表現する。Shapeはそこから派生したクラスの基底となることだけに意味がある。このことはShapeクラスの仮想関数には意義のある定義を与えることが出来ないという事実からも見て取れる:


考察:

“sensible” の意味をどう捉えるか、上では「意義のある定義」と訳してみたが、「実用的な定義」とか「具体的な定義」とか、そんな感じに訳してもいいかも。いずれにしても仮想関数には使い道が無いなどということはない。

「プログラミング言語C++第四版」について気が付いたことなど (10)

コンストラクタだって「継承」したりはしない …


20.3.6 返却型緩和

…(略)…

しかも、コンストラクタは、通常の関数とはまったく異質なものだ。特に、通常の関数はメモリ管理ルーチンを継承しないが、コンストラクタは継承する。


原文:

Furthermore, a constructor is not quite an ordinary function. In particular, it interacts with memory management routines in ways ordinary member functions don’t.


試訳:

さらに、コンストラクタは通常の関数とは全く異質なものだ。特に、それ(コンストラクタ)は通常のメンバ関数とは異なるやり方でメモリ管理ルーチンとやり取りする。


考察:

“interacts with” を”継承”と訳すのはおかしい。”interact” を “inherit” と取り違えているような気がする。