FC2ブログ

子供の落書き帳 Remix

15/4/13:ひと月に一度更新するブログになってしまっている

スポンサーサイト
--/--/--(--) --:--:--

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
  1. --/--/--(--) --:--:--|
  2. スポンサー広告

テストに便利! 実例で理解する、関数の引数の中でクラスの初期化をする方法
2018/05/02(水) 13:02:36

C++である機能を作って、それを試験したいとしよう。「入力と期待される結果」を与えて、期待通りに動いているかを確かめればよい。その確認をするために試験用の関数を作る。
具体的な例を挙げよう。「リーダブルコード」の一つの章では、適切なテストコードを書くことについて述べている。その中でこんな例が登場する。

最近のC++では、以下のように配列リテラルをそのまま引数として渡せるようになっている。

CheckScoreBeforeAfter({-5, 1, 4, -99998.7, 3}, {4, 3, 1})

(リーダブルコード p.183)



この部分の手前で書いてある内容だが、試験対象のコードは
「スコアを降順にソートして、マイナスのスコアを削除する」という処理をしている。
CheckScoreBeforeAfter関数を使えば、{-5, 1, 4, -99998.7, 3}を入れたら{4, 3, 1}が出力されることが確認できる、というわけだ。

関数の引数(実引数)の中に、試験したい内容を書いてしまえば、わざわざ一度変数を定義する必要もなくなる。
しかし、この波括弧の中で何が起きているのか、いまいちよく分からない。そこで色々と調べてみた。
クラスの初期化の例に限って説明する。(上のvectorの例までたどり着かなかった……)
また、以下の初期化は関数の引数に限らず、色々なところで使えるが、今回は関数引数に限る。
包括的な例は以下が詳しい。
統一初期化・リスト初期化の見本帳 - イグトランスの頭の中

確認した動作環境


Visual C++ 2017 Windows 64bit版

統一初期化 (uniform initialization)



まず、uniform initializationについて見てみる。日本語では「統一初期化」「一様初期化」などと呼ばれている。

一様初期化 - cpprefjp C++日本語リファレンス
から引用すると、『一様初期化は、コンストラクタの呼び出しを、リスト初期化と合わせて波カッコ { }で記述する構文である。』
この波括弧がやっていることはコンストラクタの呼び出しなのだ。

コンストラクタがない場合……何か変だ!?



まずはコンストラクタを全く書かずに実行してみよう。


class MyClassA {
public:
int x;
int y;
};

void func(MyClassA a) {
cout <<"x="<< a.x << ", y=" << a.y << endl;
}

int main()
{
func({}); // (1)
func({1}); // (2)
func({2,3});// (3)
return 0;
}

出力:
x=0, y=0
x=1, y=0
x=2, y=3



あ、あれ!? 何で??
俺の予想は「MyClassAが持っているコンストラクタは、引数のないデフォルトコンストラクタだけである。したがって、引数がint 1個の(2)、および引数がint 2個の(3)は対応するコンストラクタが無いため、コンパイルエラーになる」だったのだが……

{ }で要素を順番に記述するこの形式はC言語の構造体に似ている。
……と思って色々調べたら、おそらく上の例はアグリゲート(集成体)の初期化に該当するようだ。

アグリゲート(集成体)とは


C++11: Syntax and FeatureAggregates 集成体 - C++と色々の解説によれば、
アグリゲートとは、全ての配列と、特定の条件を満たしたクラスの総称である。クラスの条件は以下の通り。
- ユーザーが定義したコンストラクタが無い
- 全てのメンバがpublicである
- 基底クラスが無い
- 仮想関数を持たない

……なるほど、確かに上のクラスは条件を全て満たしているので、これはアグリゲートである。
アグリゲートは乱暴に言えば、「クラス特有の難しい機能を使ってないし、C言語の構造体と同等に扱っていいやろ」という感じだろうか。

アグリゲート(集成体)の初期化



アグリゲートだと何が違うのか。
それはC言語の構造体と同様に、{ }の中に要素を順番に書いて初期化することができる。(これを初期化子リスト (initializer lists)という。初期化子リスト - cpprefjp C++日本語リファレンス)
上の例1では関数の引数の中に{ }を書き、その中に要素をを書いているので、アグリゲートに対する初期化が実行される。
つまり例1は、「統一初期化子によるコンストラクタの呼び出し」ではなく、「初期化子リストによるアグリゲートの初期化」なのだ。……なんて複雑なんだろう……!!

ユーザーが定義したコンストラクタを持たず、アグリゲート(集成体)でないクラスの初期化


確認のため、アグリゲートでないクラスを同様に初期化してみよう。 privateメンバ変数を追加してみる。


class MyClassA {
public:
int x;
int y;
private:
int z;
};

void func(MyClassA a) {
cout <<"x="<< a.x << ", y=" << a.y << endl;
}

int main()
{
func({});
func({1}); //エラー!
func({2,3}); //エラー!
return 0;
}

ビルド結果:
error C2664: 'void func(MyClassA)': 引数 1 を 'initializer list' から 'MyClassA' へ変換できません。
note: コンストラクターはソース型を持てません、またはコンストラクターのオーバーロードの解決があいまいです。
error C2664: 'void func(MyClassA)': 引数 1 を 'initializer list' から 'MyClassA' へ変換できません。
note: コンストラクターはソース型を持てません、またはコンストラクターのオーバーロードの解決があいまいです。


よし、今回は予想通り。
このクラスはprivateメンバ変数を含むので、アグリゲートではない。アグリゲートではないので、初期化子リストは使えない。したがって、初期化子として指定できるのはデフォルトコンストラクタだけであり、空の波括弧以外は型が合わないのでエラーとなる。


コンストラクタを持つクラスの初期化


ここからが「統一初期化 (uniform initialization)によるクラス初期化」となる。C++11からの機能である。


class MyClassA {
public:
int x;
int y;

MyClassA(int x0) { x = x0; }
};

void func(MyClassA a) {
cout <<"x="<< a.x << ", y=" << a.y << endl;
}

int main()
{
func({}); //エラー!
func({1});
func({2,3}); //エラー!
return 0;
}

ビルド結果:
error C2664: 'void func(MyClassA)': 引数 1 を 'initializer list' から 'MyClassA' へ変換できません。
note: コンストラクターはソース型を持てません、またはコンストラクターのオーバーロードの解決があいまいです。
error C2664: 'void func(MyClassA)': 引数 1 を 'initializer list' から 'MyClassA' へ変換できません。
note: コンストラクターはソース型を持てません、またはコンストラクターのオーバーロードの解決があいまいです。


引数1個のコンストラクタだけが定義されているので、波括弧の中の変数が1個ならOK、それ以外はエラーになる。


class MyClassA {
public:
int x;
int y;

MyClassA(int x0 = -100, int y0 = -200) { x = x0; y = y0; }
};

void func(MyClassA a) {
cout <<"x="<< a.x << ", y=" << a.y << endl;
}

int main()
{
func({});
func({1});
func({2,3});
return 0;
}


もちろん、コンストラクタにデフォルト引数を指定するのと組み合わせることも可能だ。(コンストラクタのデフォルト引数は、C++の最初からある機能だ。)

おまけ:
どの部分がC++11 の機能なのかイマイチ分からないので、C++03としてコンパイルしようかと思った。しかし以下のサイトを見る限り、コンパイルオプションを変更してC++03としてコンパイルする、ということはできない。(旧バージョンのVSをインストールするという方法はある)
c++11 - Change (use older) c++ version in Visual Studio - Stack Overflow
c++ - disable c++11 features in vs2013 - Stack Overflow
ええー。GCCならオプションに「-std=c++03」と書けば一発なのに。

それでは。
  1. 2018/05/02(水) 13:02:36|
  2. プログラミング
  3. | トラックバック:0
  4. | コメント:0

コメント


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://luvtome.blog5.fc2.com/tb.php/641-d2b483c2
この記事にトラックバックする(FC2ブログユーザー)

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。