GotW #2: Temporary Objects 一時オブジェクト(勝手訳)

第2回めの GotW の翻訳です。

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


原文:http://herbsutter.com/2013/05/13/gotw-2-solution-temporary-objects/


Unnecessary and/or temporary objects are frequent culprits that can throw all your hard work—and your program’s performance—right out the window. How can you spot them and avoid them?

必要がなかったり一時的にしか使われない(もしくはその両方であるような)オブジェクトは、あなたの大変な努力を - そしてあなたのプログラムのパフォーマンスを - 台無しにすることがあります。

Problem 問題

JG Question 見習いグルへの質問


1. What is a temporary object?

1. 一時オブジェクトとは何か?

Guru Question グルへの質問


2. You are doing a code review. A programmer has written the following function, which uses unnecessary temporary or extra objects in at least three places. How many can you identify, and how should the programmer fix them?

2. あなたはコードレビューを行なっている。あるプログラマが以下のような関数を書いた。その中では不必要な一時オブジェクトや余分なオブジェクトが少なくとも3箇所で使われている。あなたはいくつ見つけられるか?そしてそのプログラマはどのようにそれを直すべきか?

string find_addr( list<employee> emps, string name ) {
    for( auto i = begin(emps); i != end(emps); i++ ) {
        if( *i == name ) {
            return i->addr;
        }
    }
    return "";
}


Do not change the operational semantics of this function, even though they could be improved.

この関数の動作上のセマンティクスを変更してはならない。たとえそれが改善につながるとしても。

Solution 解

1. What is a temporary object?

1. 一時オブジェクトとは何か?

Informally, a temporary object is an unnamed object that you can’t take the address of. A temporary is often created as an intermediate value during the evaluation of an expression, such as an object created by returning a value from a function, performing an implicit conversion, or throwing an exception. We usually call a temporary object an “rvalue,” so named because it can appear on the “r”ight hand side of an assignment. Here are some simple examples:

正式な言い方ではありませんが、一時オブジェクトとはそのアドレスを取得することができない無名オブジェクトです。一時オブジェクトはしばしば、関数からのリターン、暗黙の変換の実行もしくは例外のスローによって生成されるオブジェクトのような、式の評価の間の中間的な値として生成されます。私たちは通常、一時オブジェクトのことを rvalue と呼びます。代入文の右辺("r"ight hand side)に現れるものだからです。いくつかのシンプルな例をお見せします:

widget f();            // f は 一時的な widget オブジェクトを返す

auto a = 0, b = 1;
auto c = a + b;        // "a+b" は一時的な int オブジェクトを生成する

In contrast, in the same code we have objects like a and c that do each have a name and a memory address. Such an object is usually called an “lvalue,” because it can appear on the “l”eft hand side of an assignment.

対照的に、同じコードの中にある a や c のようなオブジェクトは名前とメモリアドレスを持ちます。そのようなオブジェクトは通常 lvalue と呼ばれます。代入文の左辺("l"eft hand side)に現れるものだからです。


That’s a simplification of the truth, but it’s generally all you need to know. More precisely, C++ now has five categories of values, but distinguishing them is primarily useful for writing down the language specification, and you can mostly ignore them and just think about “rvalues” for temporary objects without names and whose addresses can’t be taken, and “lvalues” for non-temporary objects that have names and whose addresses can be taken.

これは真実を簡略化していますが、普通の人が知っておく分にはこれで十分です。より正確には、C++ には今や5つの値のカテゴリがありますが、それらを区別するのは言語仕様を書くには便利ですが、普通の人は気にする必要がありません。とにかく "rvalue” は名前を持たない一時的なオブジェクトでそのアドレスを取得すことができない、"lvalue" は一時的ではないオブジェクトで名前を持ちアドレスを取得することができると考えてよいでしょう。


2. How many unnecessary temporary objects can you identify, and how should the programmer fix them?

2. あなたはいくつの不要な一時オブジェクトを見つけられるか?そしてそのプログラマはどのようにそれを直すべきか?

Believe it or not, this short function harbors three obvious cases of unnecessary temporaries or extra copies of objects, two subtler ones, and three red herrings.

信じるかどうかは別として、この短い関数には不要な一時オブジェクトや余分なオブジェクトのコピーが明らかなものが3つ、微妙なものが2つ、そして3つの囮が隠れています。


The parameters are passed by value.

パラメータが値で渡されている。

The most obvious extra copies are buried in the function signature itself:

最も明らかな余分なコピーは関数シグネチャ自体に含まれています:

  string find_addr( list<employee> emps, string name )

The parameters should be passed by const&—that is, const list& and const string&, respectively—instead of by value. Pass-by-value forces the compiler to make complete copy of both objects, which can be expensive and, here, is completely unnecessary.

パラメータは値ではなく const& で渡されるべきです。つまり、それぞれ const list& と const string& となります。値渡しはコンパイラに両方のオブジェクトの完全なコピーを作らせますが、それは高価なものになりえますし、完全に不要です。

Guideline: Prefer passing a read-only parameter by const& if you are only going to read from it (not make a copy of it).

ガイドライン:読み取り専用のパラメータは const& で渡すようにする。(コピーを作らない)


Pedantic note: Yes, with pass-by-value, if the caller passed a temporary list or string argument then it could be moved from rather than copied. But I’m deliberately saying “forces the compiler to make a complete copy” here because no caller is realistically going to be passing a temporary list to find_addr, except by mistake.

厳密には、値渡しであっても呼び出し元が引数として一時オブジェクトを渡したら、それはコピーされるのではなくてムーブされるかもしれません。しかし私は意図的に "コンパイラに完全なコピーを作らせる” と言いました。間違いを除いて呼び出し元が list の一時オブジェクトを find_addr に渡すことは現実的にはないからです。

Non-issue: Initializing with “=”.

問題なし:"=" による初期化

Next we come to the first red herring, in the for loop’s initialization:

次に最初の囮に差し掛かります。for ループの初期化部において:

    for( auto i = begin(emps); /*...*/ )

You might be tempted to say that this code should prefer to be spelled auto i(begin(emps)) rather than auto i = begin(emps), on the grounds that the = syntax incurs an extra temporary object, even if it might be optimized away. After all, as we saw in GotW #1, usually that extra = means the two-step “convert to a temporary then copy/move” of copy-initialization—but recall that doesn’t apply when using auto like this. Why?

あなたはもしかしたら、"このコードは auto i = begin(emps) ではなくて auto i(begin(emps)) と書くべきだよ、= シンタックスはたとえ最適化可能だとはいえ余分な一時オブジェクトを作るよ"、と言いたくなったのではないでしょうか?どのみち、GotW #1 で見たように、通常はその余分な = は2ステップの "一時オブジェクトに変換してからコピーかムーブ" のコピー初期化を意味します。しかし、それはここでのように auto を使っている時には当てはまらなかったことを思い起こしてください。なぜでしょうか?

Remember that auto always deduces the exact type of the initializer expression, minus top-level const and & which don’t matter for conversions, and so… presto! there cannot be any need for a conversion and we directly construct i.

auto は常に初期化式の正確な型を推測します。そしてトップレベルの const と & は変換には関係ありません。そして、、、そう!変換が一切不要なので i を直接構築することができます。

So there is no difference between auto i(begin(emps)) and auto i = begin(emps). Which syntax you choose is up to you, but it depends only on taste, not on temporaries or any other performance or semantic difference.

なので auto i(begin(emps)) と auto i = begin(emps) の間には何も違いがありません。どちらのシンタックスを選ぶかはあなた次第ですが、それは単に好みの問題であって、一時オブジェクトなどのパフォーマンスやセマンティックの違いはありません。

Guideline: Prefer declaring variables using auto. Among other reasons to do so, it naturally guarantees zero extra temporaries due to implicit conversions.

ガイドライン:auto を使って変数を宣言するようにする。そうするべき理由はいくつかあるが、必然的に暗黙の変換による余分な一時オブジェクトが生成されないことが保証される。

The end of the range is recalculated on each loop iteration.

範囲の終端はループの各繰り返しごとに再計算される。

Another potential avoidable temporary occurs in the for loop’s termination condition:

もうひとつの回避可能な一時オブジェクトは for ループの終了条件に見られます:

  for( /*...*/ ; i != end(emps); /*...*/ )

For most containers, including list, calling end() returns a temporary object that must be constructed and destroyed, even though the value will not change.

list を含めほとんどのコンテナにおいて、end() の呼び出しは一時オブジェクトを返します。一時オブジェクトは構築され、破棄されます。たとえその値が変化しないとしてもです。

Normally when a value will not change, instead of recomputing it (and reconstructing and redestroying it) on every loop iteration, we would want to compute the value only once, store it in a local object, and reuse it.

普通、値が変化しないときは、それをループの繰り返しごとに再計算する代わりに(そして再構築と再破棄する代わりに)、その値を一度だけ計算してそれをローカルオブジェクトとして保存しておき、再利用したいと思うはずです。

Guideline: Prefer precomputing values that won’t change, instead of recreating objects unnecessarily.

ガイドライン:値が変化しない場合は、不必要にオブジェクトを再生成するのではなく、前もって計算した値を使うようにする。

However, a caution is in order: In practice, for simple inline functions like list::end() in particular used in a loop, compilers routinely notice their values won’t change and hoist them out of the loop for you without you having to do it yourself. So I actually don’t recommend any change to hoist the end calculation here, because that would make the code slightly more complex and the definition of premature optimization is making the code more complex in the name of efficiency without data that it’s actually needed. Clarity comes first:

しかしながら、注意があります:実際には、list::end() のようなシンプルなインライン関数に対しては、特にループのなかで使われる場合、コンパイラはその値が変化しないことに普通は気づいてそれをループの外に取り出します。あなたが自分でそれをやる必要はありません。なので私は end の計算をループの外に取り出すようにとはおすすめしません。コードが複雑になりますし、早すぎる最適化は効率性の名のもとに実際には必要なデータなしに効率性の名のもとにコードをより複雑にしてしまうからです。明快さが第一です:

Definition: Premature optimization is when you make code more complex in the name of efficiency without data that it’s actually needed.

定義:早すぎる最適化 とは、必要なデータなしに効率性の名のもとにコードを複雑にしてしまうこと

Guideline: Write for clarity and correctness first. Don’t optimize prematurely, before you have profiler data proving the optimization is needed, especially in the case of calls to simple inline calls to short functions that compilers normally can handle for you.

ガイドライン:まず、明快さと正しさのために書く。最適化が必要であることを示すプロファイラのデータが得られるまでは、早すぎる最適化をしない。特にシンプルなインライン関数は通常コンパイラがそれを適切に扱ってくれる。

The iterator increment uses postincrement.

イテレータのインクリメントに後置インクリメントを使っている。

Next, consider the way we increment i in the for loop:

次は、for ループにおいて i をインクリメントする方法について考えてみよう:

  for( /*...*/ ; i++ )


This temporary is more subtle, but it’s easy to understand once you remember how preincrement and postincrement differ. Postincrement is usually less efficient than preincrement because it has to remember and return its original value.

この一時オブジェクトはより気づきにくいが、前置インクリメントと後置インクリメントがどう違うかが一度わかってしまえば理解するのはたやすいことです。後置インクリメントは通常、前置インクリメントよりも効率的ではありません。オリジナルの値を覚えておいて返さなければならないからです。

Postincrement for a class T should normally be implemented using the canonical form as follows:

クラス T に対する後置インクリメントは普通、以下の様な正規化された形式を使って実装されます。

T T::operator++(int)() {
    auto old = *this; // remember our original value
    ++*this;          // always implement postincr in terms of preincr
    return old;       // return our original value
}


Now it’s easy to see why postincrement is less efficient than preincrement: Postincrement has to do all the same work as preincrement, but in addition it also has to construct and return another object containing the original value.

もう後置インクリメントが前置インクリメントよりも効率的でない理由がお分かりでしょう。後置インクリメントは前置インクリメントと全く同じ仕事をする必要がありますが、それに加えてオリジナルの値を保持するもう一つのオブジェクトを作って返す必要もあります。

Guideline: For consistency, always implement postincrement in terms of preincrement, otherwise your users will get surprising (and often unpleasant) results.

ガイドライン:一貫性のため、常に後置インクリメントは前置インクリメントを使って実装する。そうしなければ驚くような(そしてうれしくない)結果をユーザにもたらすことになります。

In the problem’s code, the original value is never used, and so there’s no reason to use postincrement. Preincrement should be used instead. Although the difference is unlikely to matter for a built-in type or a simple iterator type, where the compiler can often optimize away the extra unneeded work for you, it’s still a good habit not to ask for more than you need.

問題のコードでは、オリジナルの値は使われていませんので、後置インクリメントを使う理由はありません。代わりに前置インクリメントが使われるべきです。組込みの型であるかシンプルなイテレータ型であるかといった違いにはほとんど関係なく、コンパイラは最適化により余計なものを取り除いてくれますが、それでも必要以上のことはやらないほうが良いでしょう。

Guideline: Prefer preincrement. Only use postincrement if you’re going to use the original value.

ガイドライン:前置インクリメントを優先的に使用せよ。オリジナルの値を使いたいときだけ後置インクリメントを使用せよ。

“But wait, you’re being inconsistent!” I can just hear someone saying. “That’s premature optimization. You said that compilers can hoist the end() call out of the loop, and it’s just as easy for a compiler to optimize away this postincrement temporary.”

"でも待って、君が言ってることは一貫性がないよ!" 誰かがそう言ったのが聞こえました。"これは早すぎる最適化ってやつだよ。コンパイラは end() をループの外に出してくれるって言ったし、この後置インクリメントの一時オブジェクトを最適化して取り除くのもコンパイラにとってはたやすいことだって言ったじゃない。"

That’s true, but it doesn’t imply premature optimization. Preferring ++i does not mean writing more complex code in the name of performance before you can prove it’s needed—++i is not more complex than i++, so it’s not as if you need performance data to justify using it! Rather, preferring ++i is avoiding premature pessimization, which means avoiding writing equivalently complex code that needlessly asks for extra work that it’s just going to ignore anyway.

それはたしかにそうですが、これは早すぎる最適化ではありません。++i を優先的に使うというのは、パフォーマンスの名のもとにそれが本当に必要であると証明される前に複雑なコードを書くこととは違います。++i は i++ よりも複雑だということはありません。これを使うのを正当化するためにパフォーマンスデータは要りません。逆に、++i を使うことは "早すぎる最悪化(pessimization)" を避けることになります。同程度の複雑さのコードを書くことで、ただ無視されるだけの余計なものを不要に作らせることを回避するからです。

Definition: Premature pessimization is when you write code that is slower than it needs to be, usually by asking for unnecessary extra work, when equivalently complex code would be faster and should just naturally flow out of your fingers.

定義:早すぎる最悪化とは、不要な余計なことをさせるなど、必要以上に遅いコードを書くこと。また同程度の複雑さのコードのほうが速く、それが自然に書くことができること。


The comparison might use an implicit conversion.

比較は暗黙の変換を伴うかもしれない。

Next, we come to this:

次は、ここです:

    if( *i == name )


The employee class isn’t shown in the problem, but we can deduce a few things about it. For this code to work, employee likely must either have a conversion to string or a conversion constructor taking a string. Both cases create a temporary object, invoking either operator== for strings or operator== for employees. (Only if there does happen to be an operator== that takes one of each, or employee has a conversion to a reference, that is, string&, is a temporary not needed.)

employee クラスは問題の中では提示されていませんが、いくつか推測することができます。このコードを動かすためには、employee は string への変換もしくは string を受け取る変換コンストラクタのいずれかを持っていなければならないでしょう。どちらのケースでも一時オブジェクトが作られ、string に対する operator== もしくは employee に対する operator== が起動されます。(operator== が参照(string&)を受け取るか、employee が参照(string&)への変換を持っている場合に限り、一時オブジェクトは必要ありません)

Guideline: Watch out for hidden temporaries created by implicit conversions. One good way to avoid this is to make constructors and conversion operators explicit by default unless implicit conversions are really desirable.

ガイドライン:暗黙の変換によって作られる、隠れた一時オブジェクトに注意しなさい。これを避けるための一つの良い方法は、コンストラクタと変換演算子を explicit にすることです。(暗黙の変換が本当に必要な場合を除く)

Probably a non-issue: return “”.

おそらく問題ではない:return ""

    return "";


Here we unavoidably create a temporary (unless we change the return type, but we shouldn’t; see below), but the question is: Is there a better way?

この場合、一時オブジェクトを作ることは不可避です(ただし戻り値の型を変更することが出来る場合を除く。しかしそうするべきではない。後述)。問題は:もっといい方法はあるか?です。

As written, return “”; calls the string constructor that takes a const char*, and if the string implementation you’re using either (a) is smart enough to check for the case where it’s being passed an empty string, or (b) uses the small string optimization (SSO) that stores strings up to a certain size directly within the string object instead of on the heap, no heap allocation will happen.

書かれてあるとおり、return ""; は string の const char* を引数にとるコンストラクタを呼びます。そしてあなたの使っている string の実装が以下のいずれかの場合、ヒープの確保は起こりません。(a) 空文字列が渡されたことをチェックするような、十分賢い実装になっている。(b) small string optimization (SSO) を使って、ある特定のサイズまでの文字列は直接 string オブジェクトの中に格納し、ヒープを使わないようになっている。

Indeed, every string implementation I checked is smart enough not to perform an allocation here, which is maximally efficient for string, and so in practice there’s nothing to optimize. But what alternatives do we have? Let’s consider two.

たしかに、私のチェックしたすべての string の実装は十分にスマートで、ここではメモリ確保は発生しないようになっていました。これは string としては最大限に効率的です。そして実際に最適化するべきものは何もありません。しかし、他の代替案はないでしょうか?2つ考えてみましょう。


First, you might consider re-spelling this as return “”s; which is new in C++14. That essentially relies on the same implementation smarts to check for empty strings or to use SSO, just in a different function—the literal operator”".

まず、return ""s; と書き換えてみることを考えることができます。これは C++14 で新しく導入されたものです。これは空文字列をチェックするスマートな実装やSSOを使うことと同じものに本質的に頼っています。違うのはリテラル演算子 operator"" です。

Second, you might consider re-spelling this as return { };. On implementations that are both non-smart and non-SSO, this might have a slight advantage over the others because it invokes the default constructor, and so even the most naïve implementation is likely not to do an allocation since clearly no value is needed.

2つ目は、return {}; と書き換えることを考えることができます。スマートでもなく SSO でもない実装においても、これは他の方法に比べてちょっとしたアドバンテージを持つかもしれません。これはデフォルトコンストラクタを起動し、明らかに値が不要なので、最もナイーブな実装であったとしてもメモリ確保をしないでしょう。

In summary, there’s no difference in practice among returning “”, “”s, or { }; use whichever you prefer for stylistic reasons. If your string implementation is either smart or uses SSO, which covers all implementations I know of, there’s exactly zero allocation difference.

まとめると、""、""s もしくは {} を返すことの間には実際的な違いはありません。スタイル上の理由で好きなものをどれでも使えば良いと思います。あなたの使っている string の実装がスマートであるか SSO を使っている場合、そしてそれは私の知る限りすべての実装をカバーしていますが、メモリ確保に関しては全く違いがありません。


Note: SSO is a wonderful optimization for avoiding allocation overhead and contention, and every modern string ought to use it. If your string implementation doesn’t use SSO (as of this writing, I’m looking at you, libstdc++), write to your standard library implementer—it really should.

注:SSO はメモリ確保のオーバーヘッドや競合を回避するための素晴らしい最適化であり、すべての近代的な string が持つべきものです。もしあなたの string の実装が SSO を使っていないなら(これを書いている時点では、libstdc++、君のことを言っているんだよ)、あなたは標準ライブラリの実装者に「マジで実装するべきだ」といってあげてください。


Non-issue: Multiple returns.

問題ではない:複数の return

    return i->addr;
    return "";


This was a second subtle red herring, designed to lure in errant disciples of the “single-entry/single-exit” (SE/SE) persuasion.

これは2つ目の微妙な罠です。”一つの入口/一つの出口" (single-entry/single-exit: SE/SE) という信条の常軌を逸した信奉者を引っ掛けるための釣り餌として用意しました。

I In the past, I’ve heard some people argue that it’s better to declare a local string object to hold the return value and have a single return statement that returns that string, such as writing string ret; … ret = i->addr; break; … return ret;. The idea, they say, is that this will assist the optimizer perform the ‘named return value optimization.’

過去に、ある人々が以下の様な議論をしているのを聞いたことがあります。すなわち、戻り値を保持するローカルの string オブジェクトを宣言して、その string を return する return 文を一つだけ持つのが良い。たとえば string ret; ... ret = i->addr; break; ... return ret; というように書く。このアイディアは、彼らが言うには、オプティマイザが 'named return value optimization(名前付き戻り値最適化)' を行うのを助けるというのです。

The truth is that whether single-return will improve or degrade performance can depend greatly on your actual code and compiler. In this case, the problem is that creating a single local string object and then assigning it would mean calling string’s default constructor and then possibly its assignment operator, instead of just a single constructor as in our original code. “But,” you ask, “how expensive could a plain old string default constructor be?” Well, here’s how the “two-return” version performed on one popular compiler last time I tried it:

真実は、ひとつの return がパフォーマンスを改善するか改悪するかというのは実際のコードとコンパイラに大きく依存しているということです。このケースでは、一つのローカル string オブジェクトを生成してそれに後から値を代入するというのは、string のデフォルトコンストラクタを呼び出し、かつ代入演算子も呼び出すという事になるでしょう。一方、オリジナルのコードではコンストラクタが一度だけ呼び出されます。"でも"、とあなたは言います。"string の単純なデフォルトコンストラクタがどれくらい高価だっていうんだい?" ええと、私が最近試した、あるポピュラーなコンパイラにおいて "2つの return" バージョンのパフォーマンスはこうでした:

  • with optimizations disabled: two-return 5% faster than a “return value” string object
  • with aggressive optimizations: two-return 40% faster than a “return value” string object
  • 最適化オフ:2つのreturn は "戻り値" string オブジェクトよりも 5% 速い
  • 最適化 MAX:2つのreturn は "戻り値" string オブジェクトよりも 40% 速い


Note what this means: Not only did the single-return version generate slower code on this particular compiler on this particular day, but the slowdown was greater with optimizations turned on. In other words, a single-return version didn’t assist optimization, but actively interfered with it by making the code more complex.

これが意味するところに注意:1つの return バージョンはこの特定の日にこの特定のコンパイラにおいて遅いコードを生成しただけではなく、最適化を ON にしたときにスローダウンが大きくなりました。言い換えると、1つの return バージョンは最適化を支援しませんでした。むしろコードを複雑にすることによって積極的に最適化の邪魔をしました。

In general, note that SE/SE is an obsolete idea and has always been wrong. “Single entry,” or the idea that functions should always be entered in one place (at their start) and not with goto jumps from the caller’s code directly to random places inside the function body, was and is an immensely valuable advance in computer science. It’s what made libraries possible, because it meant you could package up a function and reuse it and the function would always know its starting state, where it begins, regardless of the calling code. “Single exit,” on the other hand, got unfairly popular on the basis of optimization (‘if there’s a single return the compiler can perform return value optimization better’—see counterexample above) and symmetry (‘if single entry is good, single exit must be good too’) but that is wrong because the reasons don’t hold in reverse—allowing a caller to jump in is bad because it’s not under the function’s control, but allowing the function itself to return early when it knows it’s done is perfectly fine and fully under the function’s control. To put the final nail in the coffin, note that “single exit” has always been a fiction in any language that has exceptions, because you can get an early exceptional return from any point where you call something that could throw an exception.

一般的に、SE/SE は時代遅れのアイディアで、常に間違っていました。”一つの入口" 、もしくは関数には常にひとつの場所(開始地点)から入ってくるべきであって呼び出し元のコードから直接 goto ジャンプで関数本体の内側のデタラメな場所から入ってくるべきではないという考えは、コンピュータサイエンスにおける偉大で価値のある進歩です。関数は呼び出し元のコードに関係なく、常にその開始状態を知っているので、そのおかげで関数をパッケージして再利用することができてライブラリを作ることができます。一方、"一つの出口" は不当にポピュラーになってしまいました。最適化('return がひとつだけならコンパイラは戻り値の最適化をよりうまくやれる'というもの - 上の反例を参照)や対称性('一つの入口 が良いのなら、一つの出口もまた良いのだろう’)にもとづいていますが、それは間違いです。それらの理由は逆に成り立たないからです。呼び出し元からジャンプして入ってくることを許すのは悪いことです。その関数のコントロールの及ばないことだからです。しかしその関数から早く return することは、それがいつ行われるかがわかっていれば全く問題ありません。その関数のコントロール下にあることだからです。とどめに、"一つの出口" は例外機構を持ついかなる言語においても、常にフィクションに過ぎません。例外を投げる可能性のあるものを呼び出すところではどこでも例外によって関数から抜けだしてしまうことができるからです。

Non-issue: Return by value.

問題なし:値による return

Which brings us to the third red herring:

3番目の罠です:

string find_addr( /*...*/ )


Because C++ naturally enables move semantics for returned values like this string object, there’s usually little to be gained by trying to avoid the temporary when you return by value. For example, if the caller writes auto address = find_addr( mylist, “Marvin the Robot” );, there will be at most a cheap move (not a deep copy) of the returned temporary into address, and compilers are allowed to optimize away even that cheap move and construct the result into address directly.

C++ はこの string オブジェクトのような戻り値に対して当然のようにムーブセマンティクスを有効にしますので、値を返すときに一時オブジェクトを避けようとしても通常は得るものはほとんどないでしょう。たとえば、呼び出し元が auto address = find_addr(mylist, "Marvin the Robot"); と書いたとすれば、戻り値の一時オブジェクトは address に対してディープコピーではなくせいぜい安価なムーブになるでしょうし、コンパイラはその安価なムーブを最適化して取り除いて address に直接結果を構築することもできます。

But what if you did feel tempted to try to avoid a temporary in all return cases by returning a string& instead of string? Here’s one way you might try doing it that avoids the pitfall of returning a dangling reference to a local or temporary object:

もしあなたに、すべての return で string を使う代わりに string& を返すことで一時オブジェクトを回避しようとする魔が差したとしたらどうでしょう?ローカルオブジェクトや一時オブジェクトへの宙ぶらりんの(dangling)参照を返してしまう落とし穴を回避しながらあなたが試すかもしれない一つの方法はこうなるでしょう:

const string& find_addr( /* ... */ ) {
    for( /* ... */ ) {
        if( /* found */ ) {
            return i->addr;
        }
    }
    static const string empty;
    return empty;
}


To demonstrate why this is brittle, here’s an extra question:

これが脆弱であることを示すために、もう一つ問題です:

For the above function, write the documentation for how long the returned reference is valid.

上記の関数において、返された参照がどのくらいの期間有効であるかを説明するドキュメントを書きなさい。

Go ahead, we’ll wait.

さあどうぞ。待ってます。


Done? Okay, let’s consider: If the object is found, we are returning a reference to a string inside an employee object inside the list, and so the reference itself is only valid for the lifetime of said employee object inside the list. So we might try something like this (assuming an empty address is not valid for any employee):

終わりましたか?オーケー、それでは考えてみましょう:もし object が見つかったら、list の中の employee オブジェクトの中の string への参照を返すことになります。したがってその参照自身は、その list の中の employee オブジェクトの有効期間の間だけ有効です。したがってあなたはこのようなことを言ってみるかもしれません(空の住所はどんな employee に対しても有効でないと仮定します):

“If the returned string is nonempty, then the reference is valid until the next time you modify the employee object for which this is the address, including if you remove that employee from the list.”

"返される文字列が空でない場合、その参照は、その address を持っている employee オブジェクトが次に変更される時まで有効です。変更にはその employee オブジェクトが list から削除されることも含みます。”

Those are very brittle semantics, not least because the first (but far from only) problem that immediately arises is that the caller has no idea which employee that is—not only doesn’t he have a pointer or reference to the right employee object, but he may not even be able to easily figure out which one it is if two employees could have the same address. Second, calling code can be notoriously forgetful and careless about the lifetimes of the returned reference, as in the following code which compiles just fine:

とても脆弱なセマンティクスです。特に、数ある問題のうちすぐに起こる最初の問題は、呼び出し元はその employee オブジェクトが何かわかりません。正しい employee オブジェクトへのポインタも参照もわからないだけでなく、2つの employee が同じ address を持っていたらそのどちらなのかを簡単に判別することもできません。2つ目の問題は、呼び出し元のコードというのは、返されたリファレンスの有効期間について、すぐに忘れてしまって気にもしないものだということです。コンパイルは問題なく通る以下のコードのように:

auto& a = find_addr( emps, "John Doe" );  // イェイ、一時オブジェクトを回避したぜ!
emps.clear();
cout << a;                                // おっと

When the calling code does something like this and uses a reference beyond its lifetime, the bug will typically be intermittent and very difficult to diagnose. Indeed, one of the most common mistakes programmers make with the standard library is to use iterators after they are no longer valid, which is pretty much the same thing as using a reference beyond its lifetime; see GotW #18 for details about the accidental use of invalid iterators.

呼び出し元のコードがこのようなことをしていて、参照をその有効期間を超えて使っていると、そのバグはたいてい、時々しか発生せずにとても解析のしづらいものとなります。標準ライブラリでプログラマが犯すもっとも一般的な失敗は、イテレータがもう有効でないのに使ってしまうことですが、参照をその有効期間を超えて使ってしまうというのはそれととても良く似ています。誤って無効なイテレータを使ってしまうことの詳細について GotW #18 を見てください。

Summary まとめ


There are some other optimization opportunities. Ignoring these for now, here is one possible corrected version of find_addr which fixes the unnecessary temporaries. To avoid a possible conversion in the employee/string comparison, we’ll assume there’s something like an employee::name() function and that .name() == name has equivalent semantics.

他にもいくつか最適化できるところがあります。今はそれは無視して、不要な一時オブジェクトを直したバージョンの find_addr の一例をお見せします。employee と string の比較における変換を避けるために、employee::name() のような関数があって .name() == name が等価なセマンティクスを持っていると仮定します。

Note another reason to prefer declaring local variables with auto: Because the list parameter is now const, calling begin and end return a different type—not iterators but const_iterators—but auto naturally deduces the right thing so you don’t have to remember to make that change in your code.

auto を使ってローカル変数を宣言するべきであるもう一つの理由:list 引数は const になりましたので begin と end の呼び出しは違う型を返すようになりました。iterator 型ではなく const_iterator 型です。しかし auto は自然に正しい型を推測しますので、その部分について忘れずにコードを変更する必要がありません。

string find_addr( const list<employee>& emps, const string& name ) {
    for( auto i = begin(emps);  i != end(emps); ++i ) {
        if( i->name() == name ) {
            return i->addr;
        }
    }
    return "";
}

Acknowledgments 謝辞

Thanks in particular to the following for their feedback to improve this article: “litb1,” Daan Nusman, “Adrian,” Michael Marcin, Ville Voutilainen, Rick Yorgason, “kkoehne,” and Olaf van der Spek.

この記事をより良くするための以下の各位からのフィードバックに対し、特に感謝する: “litb1,” Daan Nusman, “Adrian,” Michael Marcin, Ville Voutilainen, Rick Yorgason, “kkoehne,” そして Olaf van der Spek。