「プログラミング言語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” と取り違えているような気がする。

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

いや、大変だよ …


19. 2. 5  メモリ確保と解放

…(略)…

広域のoperator new()とoperator delete()を置きかえるのは、それほど大変ではないが、お勧めはしない。というのも、デフォルトの振舞いを想定しているプログラマがいるかもしれないし、広域のoperatornew()とoperatordelete()を置きかえているかもしれないからだ。


原文:

Replacing the global operator new() and operator delete() is not for the fainthearted and
not recommended. After all, someone else might rely on some aspect of the default behavior or might even have supplied other versions of these functions.


考察:

“is not for the fainthearted” は「気の弱い人向きではない」という意味なので、要するに「大変だよ」ということ。意味が逆になってる。訳すなら「広域のoperator new()とoperator delete()を置きかえるのは、気の弱い人向きではないし、お勧めでもない」か。

“or might even have supplied other versions of these functions.” は、こちらは意味はあってるのだけれども、「ひょっとしたらこれらの関数の別バージョンを提供しているかもしれない」と素直に訳した方が分かり易くないだろうか。

 

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

特に、~なので、ということではなく


18.2.5  名前空間内の演算子

…(略)…

標準のiostreamライブラリは、組込み型出力用のメンバ関数<<を定義しているので、ユーザはostreamクラスを変更することなく、ユーザ定義型出力用の<<を定義できる ( § 38.4.2)。

18.3 複素数型


原文:

In Particular, the standard iostream library defines << member functions to output built-in types, and a user can define << to output user-defined types without modifying class ostream (§38.4.2)。


試訳:

とりわけ、標準iostreamライブラリは組み込みタイプ向けの出力用に<<メンバ関数群を定義しているが、ユーザはユーザ定義型の出力を行なう<<を、ostream クラスを変更することなく定義することが出来る。


考察:

どうも In particular の据わりが悪いが、要するに、iostreamクラスには沢山の組み込み型用のoperator<<()があるけれども、そんなの気にせずにiostreamクラスの外でユーザー定義型向けのoperator<<()は幾らでも定義できるよ、ということ。

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

動かしてみると …


15. 4. 2   初期化 と 並行 処理

次 の 例 を 考えよ う:

int x = 3;
int y = sqrt(++ x);

x と y の 値 は いくつ に なる だろ う か?   その 答え は 明らか に “3 と 2 !” だ。


そうかなあ? と思って main()を書いて動かしてみると …

#include <iostream>
#include <math.h>

using namespace std;

int x = 3;
int y = sqrt(++x);

int main(int,char**)
{
  cout << "x=" << x << ", y=" << y << endl;
  return 0;
}

案の定、

x = 4, y = 2

と表示されるのであります。(Visual C++ 2013で確認)


考察:

int y = sqrt(x+1);

なら疑問の余地無く“3 と 2 !” なんですけどね。

xが最初に初期化された時点(注)では 3 なので、だから x は3だと言えなくもない気はしますけど、普通は main に飛んでくる前の初期化の細かなタイミングや順序なんて誰も気にしませんし、外部変数の初期値というのはmainに制御が渡るまでに確定している値と考えるのが妥当だと思うので、y=sqrt(++x)でxが「幾つになるだろうか」と聞かれれば、4と答えるのが正解だと私は思います。

注: 「静的に割り当てられる定数式によってオブジェクトの初期化が行なわれるのはリンク時だから、x は 3 になる」とありますが、そうとは限りません。プログラムコードを不揮発性メモリ(ROMとか)に置く必要がある環境ではリテラルや変数の初期値も一緒にROMに置く必要があります。しかしながら、ROMに置いたままでは変数の値を変更できませんから、初期値はROM上に置いておき、それをmainの前の準備処理で実際の変数の場所(RAM)にコピーするというやり方が一般的だと思います。この場合、外部結合の変数 x の初期化が行なわれるのはリンク時ではなく、プログラムの実行時、つまり main の前です。

でもそもそも、extern 変数の初期化で他のextern変数の値を変更するようなコード書いちゃ駄目ですよね。

この節ではマルチスレッド環境ではもっと色々なことが起こり得るって書いてありますが、静的か動的かには関わらず、外部結合の変数が初期化されるタイミングでは通常はメインスレッド以外は動いていませんし、もし外部結合の変数のコンストラクタで幾つかスレッドを起動していたとしても(=mainの前に走っているスレッドがあったとしても)、int y=sqrt(++x) の初期化のためのコードがそれらのスレッドの起動の都度実行されるわけではありません。外部結合の変数の初期化は一回だけです。なので、「yがsqrt(4)にもsqrt(5)にも」とはならないのではないかと思います。

むしろそれよりは、mainの前に走り出すようなスレッドでは少なくとも動的に初期化される外部結合の変数の初期化がスレッドが走り始めたタイミングで全部は終わっていない可能性があり、それはそれで危ない話ではないかと思われ、なのでコンストラクタでスレッドを起動するのはやめておいた方が無難でしょう。

 

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

分かりにくい訳


15.3.2.1 電卓プログラムの残りのモジュール

電卓プログラムの残りのモジュールも、パーサと同様に構成できる。ただし、いずれも小さいので、専用の _impl.h は不要だ。専用の_impl.hが必要になるのは、論理モジュールの実装が、共有コンテキスト(とユーザーに提供する機能)を必要とする関数が複数個になる場合だけである。


原文:

The remaining calculator modules can be organized similarly to the parser. However, those modules are so small that they don’t require their own _impl.h files. Such files are needed only where the implementation of a logical module consists of many functions that need a shared context (in addition to what is provided to users).


試訳:

電卓プログラムの残りのモジュールも、パーサと同様に構成できる。ただし、いずれも小さいので、専用の _impl.h のようなファイルは不要だ。このようなファイルは論理モジュールの実装が(ユーザに提供されるものとは別の)共有コンテキストを必要とする多数の関数により構成される場合にのみ必要となる。


考察:

邦訳では in addition to が暗に意味することがはっきり読み取れない。関数が幾つあっても、ユーザに提供するコンテキストと同じファイルで共有コンテキストを提供してよいなら 個別の _impl.h のようなファイルは必要無いし、また、単に「複数個」では 個別の _impl.h を使う根拠としては弱い。privateにしか共有されない情報が沢山の関数に跨っている時に限り、個別の _impl.h のようなprivateな情報共有の手段が必要になるというのが、たぶん著者の言いたいことだと思う。