読者です 読者をやめる 読者になる 読者になる

GotW #1 Solution: Variable Initialization – or Is It?(勝手訳:変数の初期化-もしくは?)

前回の投稿から少し間が開いてしまいましたが、第1回めの GotW の翻訳です。

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


原文:http://herbsutter.com/2013/05/09/gotw-1-solution/

This first problem highlights the importance of understanding what you write. Here we have a few simple lines of code—most of which mean something different from all the others, even though the syntax varies only slightly.

この最初の問題は、あなたが書くものを理解することの重要性を浮き彫りにします。数行のシンプルなコードを提示しますが、ほとんどの行はたとえシンタックス上の違いがわずかだとしても他のすべての行と何かしら異なる意味を持ちます。

Problem 問題

JG Question 見習いグルへの質問

1. What is the difference, if any, among the following?
1. 以下のうち、違いがあるとしたらそれは何か?

widget w;                   // (a)
widget w();                 // (b)
widget w{};                 // (c)
widget w(x);                // (d)
widget w{x};                // (e)
widget w = x;               // (f)
widget w = {x};             // (g)
auto w = x;                 // (h)
auto w = widget{x};         // (i)
Guru Questions

グルへの質問

2. What do each of the following lines do?
2. 以下のそれぞれの行は何をするか?

vector<int> v1( 10, 20 );   // (a)
vector<int> v2{ 10, 20 };   // (b)


3. Besides the cases above, what other benefits are there to using { } to initialize objects?
3. 上記のケース以外で、オブジェクトを初期化するために {} を使うことにどのような効能があるか?


4. When should you use ( ) vs. { } syntax to initialize objects? Why?
4. オブジェクトを初期化するために {} ではなく () を 使うべきときはいつか?それはなぜか?



Solution 解答

This puzzle demonstrates several things:

  • The difference between default initialization, direct initialization, copy initialization, and list initialization.
  • The difference between using ( ) and using { } for initialization.
  • A red herring that isn’t initialization at all, and which modern C++ entirely avoids.

この問題はいくつかのことを明らかにします:

  • デフォルト初期化、ダイレクト初期化、コピー初期化、およびリスト初期化の違い
  • 初期化に () を使うことと {} を使うことの違い
  • 全くもって初期化ではないという罠と、モダンな C++ がそれを完全に回避していること

But, most important of all: If you stick to two simple Guidelines, which we’ll cover in #4, you can ignore most of these cases and the rules are pretty simple and deliver efficient performance by default.

しかし最も重要なのは、あなたが #4 でカバーされる 2 つのシンプルなガイドラインさえ守っていれば、これらのケースのほとんどを無視することができるということと、ルールはとてもシンプルであるということと、デフォルトで効率面でのパフォーマンスをもたらしてくれるということです。


1. What is the difference, if any, among the following?
1. 以下のうち、違いがあるとしたらそれは何か?

Let’s consider the cases one by one.

それぞれのケースを1つずつ考えてみましょう。

(a) is default initialization.
(a) はデフォルト初期化。

widget w;                   // (a)

This code declares a variable named w, of type widget. For most types, it is initialized using the default constructor widget::widget().

このコードは widget 型の w という名前の変数を宣言します。ほとんどの型において、デフォルトコンストラクタ widget::widget() を使って初期化されます。


Note that w is not initialized and contains garbage values if widget happens to be a built-in type like int, or a simple “int-like” class type with what’s called a “trivial” default constructor—a type that relies on the compiler-generated default constructor, has no virtual functions or virtual base classes or data member initializers, and all its bases and members satisfy the same restrictions.

widget が int のような組み込み型である場合、もしくはシンプルな "int 風" クラス型である場合、w は初期化されずゴミの値を持つということに注意してください。ここでいう "int 風" クラス型とは、"trivial" デフォルトコンストラクタをもち、コンパイラの生成するデフォルトコンストラクタに依存し、仮想関数や仮想基底クラスやデータメンバイニシャライザを持たず、そのすべての基底クラスとメンバが同じ制約を満たすものです。

(b) is a “vexing” red herring, now mostly a historical curiosity.
(b) はウザい罠であり、現時点ではほとんど歴史的な珍しい存在です。

widget w();                 // (b)


This is a pre-modern C++ pitfall: At first glance, it may look like just another variable declaration calling a default constructor widget::widget(); in reality, thanks to a grammar ambiguity, it’s a function declaration for a function named w that takes no parameters and returns a widget object by value. (If you can’t see this at first, consider that the above code is no different from writing something like int f(); which is clearly a function declaration.)

これは前近代的 C++ の落とし穴です。一見、デフォルトコンストラクタ widget::widget() を呼んでいる他の変数宣言と同じように見えます。実際には、文法の曖昧さのため、これは w という名前で引数をとらず widget オブジェクトを値で返す関数の宣言です。(もし最初はそう見えなかったとしても、上記のコードは int f(); のような明らかな関数宣言と何も違いがないということを考えてみてください)

Lest you think “aw, but those ( ) parentheses are redundant, it’s the programmer’s own fault for not just writing widget w; there!”, note that the same problem arises in those occasional cases where you think you’re initializing a variable with temporary objects:

ここであなたが「あぅ、でもその () は冗長だから、それは widget w; って書かなかったプログラマの過失だよね!」と考えるといけないので、もう一つの例をお見せします。これはテンポラリオブジェクトで変数が初期化されると考えられるかもしれませんが同じ問題が起こるケースです。

// 同じ問題(gadget と doodad は型)
//
widget w( gadget(), doodad() );  // 落とし穴: 変数宣言ではない

Scott Meyers long ago named this “C++’s most vexing parse,” because the standard resolves the parsing ambiguity by saying: “if it can be a function declaration, it is.”
スコット・メイヤーズはずっと昔、これを "C++ の最もウザいパース" と呼びました。C++ 標準はパース時の曖昧さについて、こう言って解決しているからです:"もしそれが関数宣言でありうるなら、それは関数宣言である"


The good news is that this is now mostly a historical curiosity, not something you should encounter in new code, because C++11 removed this pitfall. Note that C++11 does not change the meaning of the code—C++11 has great backward compatibility with C++98, including that this ambiguity still has the same meaning it always had. Rather, C++11 solves this by providing a syntax that supersedes case (b) in nearly all cases, so that we don’t need to ever fall into this pit anymore:

良い知らせは、これはすでに歴史的に珍しいものとなっていて、あなたが新しいコードを書いている時に出会うべきものではないということです。C++ はこの落とし穴を取り除きました。といっても、C++11 はそのコードの意味を変えてはいないことに注意してください。C++11 は C++98 に対し素晴らしい後方互換性を持っています。この曖昧さについてもこれまでと同じ意味を持っています。C++11 はほとんどすべてのケースでケース (b) に取って代わるシンタックスを提供することでこれを解決します。これでもう私たちはこの落とし穴にこれ以上ハマる必要はなくなるのです:

(c) is non-vexing and clear.
(c) はウザくないし明快。

widget w{};                 // (c)


Here we have the first reason to prefer { } to ( ): For any class type widget, line (c) does the “best parts” of (a) and (b)—it always initializes the variable, and is never ambiguous with a function declaration. No vex, no fuss, no muss.

これが () よりも {} を好むべき第一の理由です。いかなるクラス型 widget に対しても、行 (c) は (a) と (b) の "良いとこ取り" をします。常に変数を初期化し、関数宣言と曖昧ではありません。ウザくないし、面倒なことは何もありません。

“Aha, but wait, it’s not that simple!” someone might object. “What if widget has a constructor that takes a std::initializer_list? Those are greedy (preferred), so if widget has one of those wouldn’t this call that?”

"アハ、でも待って、そんなにシンプルじゃないよ!" 誰かが異議を唱えるかもしれません。"widget がstd::initializer_list を取るコンストラクタを持っていたらどう?そいつは優先されるから、widget がそれを持ってたらそっちが呼ばれるんじゃないの?"


The answer is no, this really is just as simple as it looks, because the standard is explicit that an empty { } list means to call the default constructor if available. However, it’s good to be aware of initializer_lists, so let’s talk about those next.

答えは No です。これは見たとおりシンプルです。標準には、空の {} リストはデフォルトコンストラクタがあればそれを呼ぶと明示されているからです。しかしながら、initializer_list に注意するのはいいことです。というわけで次はそれについて話しましょう。

(d) and (e) are direct initialization.
(d) および (e) はダイレクト初期化。

Now let’s consider cases where we actually initialize w from some existing variable:

今度は w を何らかの既存の変数から初期化する場合を考えましょう:

widget w(x);                // (d)
widget w{x};                // (e)


Assuming x is not the name of a type, these are both direct initialization. That’s because the variable w is initialized “directly” from the value of x by calling widget::widget(x). If x is also of type widget, this invokes the copy constructor. Otherwise, it invokes a converting constructor.

x は型の名前ではないとすると、両方ともダイレクト初期化です。widget::widget(x) を呼ぶことで x の値から w が "直接" 初期化されるからです。x も widget 型の変数であるとするならば、これはコピーコンストラクタを起動します。そうでなければ変換コンストラクタを起動します。


However, note that the syntax {x} creates an initializer_list. If widget has a constructor that takes an initializer_list, that constructor is preferred; otherwise, if widget has a constructor that takes whatever type x is (possibly with conversions), that constructor is used.

しかしながら、シンタックス {x} は initializer_list を生成することに注意してください。widget が initializer_list を取るコンストラクタを持っている場合、そのコンストラクタが優先されます。それ以外の場合、widget が(変換を伴ったとしても)x の型を取るコンストラクタを持っているなら、そのコンストラクタが使用されます。


There are two major differences that make (e) superior to (d): First, like (c), syntax (e) is unambiguous and avoids the vexing parse. If x is a type name, then (d) is a function declaration even if there is also a variable named x in scope (see above), whereas (e) is never a function declaration.

(d) よりも (e) が優れている2つの大きな違いがあります:一つ目は、(c) と同様、シンタックス (e) は曖昧ではなく、ウザいパースを回避します。x が型の名前である場合、(d)は関数宣言になってしまいます。x という名前の変数がスコープ内にあったとしてもです(上記を参照)。それに対し、(e) は関数宣言になることはありません。


Second, syntax (e) is safer because it does not allow narrowing (a.k.a. “lossy”) conversions that are otherwise allowed for some built-in types. Consider:

2つ目に、シンタックス (e) はより安全です。ナローイング(narrowing) (データ損失を伴う) を許していないからです。

int i1( 12.345 );           // ok: .345 は捨てられてしまいます。いずれにせよ好ましくありません
int i2{ 12.345 };           // error: データ損失を伴う暗黙のナローイングが起こりうる


(f) and (g) are copy initialization and copy list initialization.
(f) と (g) はコピー初期化とコピーリスト初期化

This brings us to our final two non-autocases:

これは auto を使っていない残り2つのケースです:

widget w = x;               // (f)


This is called “copy initialization.” Conceptually, the variable w is initialized using widget‘s move or copy constructor, possibly after calling another function to convert the argument implicitly (explicit conversions won’t be invoked here).

これは"コピー初期化"と呼ばれます。概念的には、変数 w は widget のムーブコンストラクタかコピーコンストラクタを使って初期化されます。場合によっては引数を暗黙的に変換するための他の関数を呼んだ後でかもしれません(明示的変換はここでは起動しません)。

Common Mistake: This is always initialization; it is never assignment, and so it never calls T::operator=(). Yes, I know there’s an “=” character in there, but don’t let that throw you — that’s just a syntax holdover from C, not an assignment operation.

ありがちな間違い:これは常に初期化です。代入ではありません。T::operator=() を呼び出しもしません。そう、”=” の文字がそこにあるのにもかかわらずです。これは C から持ち越されたただの文法であり、代入操作ではありません。

Here are the semantics:

セマンティクスはこうです:

If x is of type widget, line (f) means the same as (d) widget w(x); except that explicit constructors cannot be used. It’s guaranteed that only a single constructor is called.

x が widget 型である場合、行(f) は (d) widget w(x); と同じ意味になります。ただし、explicit コンストラクタが使われることはありません。単一のコンストラクタのみが呼び出されることは保証されています。


If x is of some other type, conceptually the compiler first implicitly converts x to a temporary widget object, then move-constructs w from that temporary rvalue, using copy construction as “the slow way to move” as a backup if no better move constructor is available. Assuming that an implicit conversion is available, (f) means the same as widget w( widget(x) );.

x が他の何かの型である場合、概念的にはコンパイラはまず暗黙的に x をテンポラリな widget 型のオブジェクトに変換し、それからそのテンポラリな rvalue から w をムーブコンストラクトします。もしちょうど良いムーブコンストラクタがない場合、バックアップとして "ムーブするための遅い方法" としてコピーコンストラクションが使われます。暗黙の変換があるならば、(f) は widget w(widget(x)); と同じになります。


Note that I said “conceptually” a few times above. That’s because practically compilers are allowed to, and routinely do, optimize away the temporary and, if an implicit conversion is available, convert (f) to (d), thus optimizing away the extra move operation. However, even when the compiler does this, the widget copy constructor must still be accessible, even if is not called—the copy constructor’s side effects may or may not happen, that’s all.

上記で何度か "概念的に" といったことに注意してください。”実際的には” コンパイラはそのテンポラリを最適化して取り除くことが許されていますし、普通のコンパイラはそうしますし、もし暗黙の変換があれば (f) を (d) に変換しますので、そのムダなムーブ操作が最適化されて取り除かれます。しかしながら、コンパイラがそうするからといって、widget コピーコンストラクタはアクセス可能でなければなりません。たとえ呼ばれることがなく、コピーコンストラクタの副作用があろうがなかろうが。以上。


Now note the related syntax that adds “=“:

さて、今度は関連するシンタックスとして "=" を加えたものに着目しましょう。

widget w = {x};             // (g)


This is called “copy list initialization.” It means the same as widget w{x}; except that explicit constructors cannot be used. It’s guaranteed that only a single constructor is called.

これは "コピーリスト初期化" と呼ばれます。これは widget w{x}; と同じ意味です。ただし、explicit コンストラクタは使われません。単一のコンストラクタのみが呼び出されることが保証されています。

(h) and (i) are also copy initialization, but simpler.
(h) と (i) もコピー初期化ですが、よりシンプル

auto w = x;                 // (h)
auto w = widget{x};         // (i)

The semantics are just like (f) and (g), except simpler to teach, learn, and use because using auto guarantees the right-hand expression’s type will be deduced exactly. Note that the (i) syntax works fine for both implicit and explicit conversions.

セマンティクスは (f) や (g) と同様ですが、教えたり習ったり使ったりするにはシンプルな点が異なります。auto を使っているので右辺の式の型が推定されることが保証されるからです。(i) シンタックスは暗黙のおよび明示的な変換が共にうまく働くことに注意してください。


Line (h) means the same as (d), type_of_x w(x);. Only a single copy constructor is called. This is guaranteed to stay true as the program evolves: Because line (h) does not commit to an explicit type, it is guaranteed to be both maximally efficient because there can be no conversion involved, and maximally robust under maintenance as the type of w “auto”-matically tracks the type of x which may change as the program is maintained.

行 (h) は (d) と同じ意味で、<xの型> w(x); です。一度だけコピーコンストラクタが呼び出されます。これはプログラムが変更されても真であることが保証されます。行 (h) は明示的な型を確定しておらず、変換が発生しないので最大限に効率的であることと、プログラムが保守されて x の型が変わっても w の型が自動的に追随するので最大限にロバストであるからです。

Line (i) is the most consistent spelling when you do want to commit to a specific type and explicitly request a conversion if needed, and once again the { } syntax happily avoids lossy narrowing conversions. In practice on most compilers, only a single constructor is called—similarly to what we saw with (f) and (g), conceptually there are two constructor calls, a converting or copy constructor to create a temporary widget{x} followed by a move to move it to w, but compilers routinely elide the latter.

行 (i) は、特定の型を確定したい場合に最も一貫性のある書き方です。必要に応じて明示的な型変換が行われ、なおかつデータ損失を伴うナローイング変換を避ける {} が使われています。実際にはほとんどのコンパイラは、(f) や (g) で見てきたのと同様、コンストラクタを一度だけ呼び出します。概念的には2回のコンストラクタ呼び出し - widget{x} の一時オブジェクトを生成するための変換コンストラクタかコピーコンストラクタと、wにそれをムーブするためのムーブコンストラクタ - がありますが、コンパイラはお決まりのパターンとして後者を取り除きます。

In general, I recommend that you try out these two forms, and increasingly prefer using them as you grow comfortable with them. I’m at the point where I’m now inclined to write virtually all of my local variable declarations this way. (I know some of you will be skeptical about this broad claim—more on “the auto question” in another GotW.)

一般的に、これらの2つの形をお試しになることをお勧めします。これらを好んで使うことが多くなるに従い、より満足度も増加することでしょう。私自身、ローカル変数を宣言するほとんどすべての箇所でこの方法を使うようになりました。(この口幅ったい言い方に対して懐疑的な方もいるかもしれません。"auto" についての GotW をお楽しみに)


2. What do each of the following lines do?
2. 以下の各行は何をするか?

In the Question 2 code, we’re creating a vector and passing the arguments 10 and 20 to its constructor—in the first case as ( 10, 20 ) and in the second case as { 10, 20 }.

質問2のコードでは、vector を生成して引数 10 と 20 をそのコンストラクタに渡しています。1つ目では (10, 20) として、2つ目では {10,20} として。

Both will call a constructor, but which one(s)? Well, vector has several constructors that can take two parameters, but only two could be correctly called with the parameters 10 and 20. Ignoring defaulted optional allocator parameters for simplicity, the two constructors are:

両方ともコンストラクタを呼び出します。ではどのコンストラクタを呼び出すのでしょう?ええと、vector は 2 つの引数を受け取るいくつかのコンストラクタを持っていますが、引数 10 と 20 で正しく呼び出されるものは 2 つだけです。デフォルトで指定されるオプションのアロケータ引数は、簡素化のために無視します。その2つのコンストラクタとは:

vector( size_t n, const int& value );    // A: value の n 個のコピー
vector( initializer_list<int> values );  // B: values のコピー


There are two simple C++ rules that tell us which one will be called for the code in question:

  • The syntax { /*…*/ } used in an expression context gives you an initializer_list.
  • Constructors that take an initializer_list are preferred over other constructors, and so can hide other constructors that might otherwise be viable.

2つのシンプルな C++ のルールが、質問のコードに対してどのコンストラクタが呼ばれるのかを教えてくれます。


Armed with those two tidbits, the answer is simple:

この2つで武装して、答えはシンプルに:

vector<int> v1( 10, 20 );    // (a) A が呼ばれる: 値 20 のコピー が 10 個
assert( v1.size() == 10 );

vector<int> v2{ 10, 20 };    // (b) B が呼ばれる: 値 10 と 20
assert( v2.size() == 2 );


3. Besides the cases above, what other benefits are there to using { } to initialize objects?
3. 上記のケース以外で、オブジェクトを初期化するために {} を使うことにどのような効能があるか?

For one thing, it’s called “uniform initialization” because it’s, well, uniform—the same for all types, including aggregate structs and arrays and std:: containers, and without the “vexing parse” annoyance:

一つとして、”単一形式(uniform)初期化" があります。それは、構造体や配列や std:: コンテナを含むすべての型に対して同じ形式が使えて、しかもあの "ウザいパース" が起こりません。

struct mystruct { int x, y; };

// C++98
rectangle w( origin(), extents() );       // おっと、ウザいパース
complex<double> c( 2.71828, 3.14159 );
mystruct        m = { 1, 2 };
int             a[] = { 1, 2, 3, 4 };
vector<int>     v;                              // 困った、初期化するには
for( int i = 1; i <= 4; ++i ) v.push_back(i);   //   もっとコードが必要だ

// C++11 (注意: "=" は必須ではない)
rectangle       w   = { origin(), extents() };
complex<double> c   = { 2.71828, 3.14159 };
mystruct        m   = { 1, 2 };
int             a[] = { 1, 2, 3, 4 };
vector<int>     v   = { 1, 2, 3, 4 };

And note that this isn’t just an aesthetic issue. Consider writing generic code that should be able to initialize any type… and while we’re at it, let’s gratuitously use perfect forwarding as an example:

これは美的問題だけにとどまりません。どんな型でも初期化できるべきであるようなジェネリックなコードを書くことを考えてみましょう。... 忙しいのでとにかくパーフェクトな forwarding を例として取り上げましょう:

template<typename T, typename ...Args>
void forwarder( Args&&... args ) {
    // ...
    T local = { std::forward<Args>(args)... };
    // ...
}
forwarder<int>            ( 42 );                  // ok
forwarder<rectangle>      ( origin(), extents() ); // ok
forwarder<complex<double>>( 2.71828, 3.14159 );    // ok
forwarder<mystruct>       ( 1, 2 );                // ok {} のおかげ
forwarder<int[]>          ( 1, 2, 3, 4 );          // ok {} のおかげ
forwarder<vector<int>>    ( 1, 2, 3, 4 );          // ok {} のおかげ


The last three lines would not be legal if forwarder used ( ) initialization syntax internally.

最後の3行は forwarder が () 初期化シンタックスを内部的に使用していると、成立しません。


The new { } syntax works pretty much everywhere, including to initialize members:

新しい {} シンタックスはどこででもうまく動きます。メンバの初期化のときも:

widget::widget( /*...*/ ) : mem1{init1}, mem2{init2, init3} { /*...*/ } 


And, as icing on the take, it’s often just plain convenient to pass function arguments, or return a value, without a type-named temporary:

関数の引数に渡したり、値を返したりするときに、一時オブジェクトの型名を書かなくて良くなるので素直に便利です。

void draw_rect( rectangle );

draw_rect( rectangle(origin, selection) );         // C++98
draw_rect({ origin, selection });                  // C++11

rectangle compute_rect() {
    // ...
    if(cpp98) return rectangle(origin, selection);  // C++98
    else      return {origin, selection};           // C++11
}


4. When should you use ( ) vs. { } syntax to initialize objects? Why?
4. オブジェクトを初期化するために {} ではなく () を 使うべきときはいつか?それはなぜか?


Here’s the simple guideline:

シンプルなガイドライン:


Guideline: Prefer to use initialization with { }, such as vector v = { 1, 2, 3, 4 }; or auto v = vector{ 1, 2, 3, 4 };, because it’s more consistent, more correct, and avoids having to know about old-style pitfalls at all. In single-argument cases where you prefer to see only the = sign, such as int i = 42; and auto x = anything; omitting the braces is fine. …

ガイドライン:{} による初期化を好んで使え。たとえばvector v = { 1, 2, 3, 4 };もしくはauto v = vector{ 1, 2, 3, 4 };のように。より一貫性があり、より正しく、古いスタイルの落とし穴についての知識を全く持たなくてもよくなる。引数が1つだけのケースでは、= 記号を使いたくなるが、この場合はint i = 42;やauto x = anything;のように {} を使わなくても構わない。


That covers the vast majority of cases. There is only one main exception:

これはほとんどのケースをカバーします。1つだけの主要な例外は:


… In rare cases, such as vector v(10,20); or auto v = vector(10,20);, use initialization with ( ) to explicitly call a constructor that is otherwise hidden by an initializer_list constructor.

...レアなケースとして、vector v(10,20);やauto v = vector(10,20);のように () による初期化を使う。そうしなければ、初期化リストコンストラクタによって隠されてしまうコンストラクタを明示的に呼び出すことができない。

However, the reason this should be generally “rare” is because default and copy construction are already special and work fine with { }, and good class design now mostly avoids the resort-to-( ) case for user-defined constructors because of this final design guideline:

しかしながら、これが一般的には "レア" なのは、デフォルトコンストラクタとコピーコンストラクタがすでに特別であり、{} でうまく動くからで、よいクラスのデザインはいまやこの最後のデザインガイドラインのためにユーザ定義のコンストラクタのために () を使うことをほとんど避けています。


Guideline: When you design a class, avoid providing a constructor that ambiguously overloads with an initializer_list constructor, so that users won’t need to use ( ) to reach such a hidden constructor.

ガイドライン:クラスをデザインするとき、initializer_list コンストラクタで曖昧にオーバーロードするようなコンストラクタを提供することを避け、ユーザがそのような隠されるコンストラクタを呼び出すために () を使う必要がないようにする。


Acknowledgments
謝辞

Thanks in particular to the following for their feedback to improve this article: Michal Mocny, Jay Miller, “Alexey,” “praetorian20,” Francisco Lopes, “Neil,” Daryle Walker.

この記事をより良くするための以下の各位からのフィードバックに対し、特別に感謝する:Michal Mocny, Jay Miller, “Alexey,” “praetorian20,” Francisco Lopes, “Neil,” Daryle Walker

Exceptional C++―47のクイズ形式によるプログラム問題と解法 (C++ in‐Depth Series)

Exceptional C++―47のクイズ形式によるプログラム問題と解法 (C++ in‐Depth Series)

  • 作者: ハーブサッター,浜田光之,Harb Sutter,浜田真理
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/11
  • メディア: 単行本
  • 購入: 9人 クリック: 134回
  • この商品を含むブログ (62件) を見る

Exceptional C++ Style―40のクイズ形式によるプログラム問題と解法=スタイル編 (C++ in‐Depth Series)

Exceptional C++ Style―40のクイズ形式によるプログラム問題と解法=スタイル編 (C++ in‐Depth Series)

  • 作者: ハーブサッター,浜田光之,Herb Sutter,浜田真理
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2006/09
  • メディア: 単行本
  • 購入: 3人 クリック: 32回
  • この商品を含むブログ (29件) を見る

More Exceptional C++ さらに40のクイズ形式によるプログラム問題と解法 (C++ In‐Depth Series)

More Exceptional C++ さらに40のクイズ形式によるプログラム問題と解法 (C++ In‐Depth Series)

  • 作者: ハーブサッター,Herb Sutter,浜田光之,浜田真理
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2008/11/05
  • メディア: 単行本(ソフトカバー)
  • 購入: 2人 クリック: 32回
  • この商品を含むブログ (19件) を見る