この記事は初心者 C++er Advent Calendar 2015の2日目の記事です。


C++11で統一初期化の構文、加えてリスト初期化による、波括弧{}による初期化が導入されました。「統一」と冠しているように、いろいろなところでこの構文が使えるようになっています。そこで、C++でオブジェクトの初期化を行う場面それぞれについて、波括弧による初期化の例を書きました。

おさらい

これまで、C++の初期化は型に応じて様々でした。

int a = 0; // 組み込み型
int b[] = { 1, 2, 3 }; // 配列
// std::vector<int> c = { 1, 2, 3 };とは書けなかった (C++03)
div_t d = { 1, 2 }; // 集成体の初期化
fstream d("nul", std::ios::binary); // コンストラクタに実引数を与える

これらをすべて{ … }で書けるようになりました。それだけでなく、new式や一時オブジェクトの作成など、様々な場面でこの書き方が使えるようになったのです。

場面別、初期化の見本

波括弧での初期化の例を順に書いています。

変数の初期化・代入

1つ目は変数の初期化、2つ目は代入です。

#include <fstream>
#include <vector>
#include <cstdlib> // struct div_tがここにある
#include <utility>
 
using namespace std;
 
int main()
{
  // 変数の初期化
  int a{ 1 };
  int b[]{ 1, 2, 3 };
  vector<int> c{ 1, 2, 3 };
  div_t d{ 1, 2 };
  pair<int, int> e{ 1, 2 };
  fstream f{ "nul", ios::binary };
 
  // 代入
  a = { 1 };
  // b = { 1, 2, 3 }; 配列は代入できない
  c = { 1, 2, 3 };
  d = { 1, 2 };
  e = { 1, 2 };
  // コンストラクタがexplicitの場合はダメ。
  // f = { "nul", ios::binary };
}

以下のように、クラスの非staticメンバ変数の初期化も同じく可能です。ここでは、丸括弧によるコンストラクタ引数の指定ができないことに注意してください。

#include <fstream>
#include <vector>
#include <cstdlib>
#include <utility>
 
class A
{
  int a{ 1 };
  int b[3]{ 1, 2, 3 }; // 要素数の指定が必要。
  vector<int> c{ 1, 2, 3 };
  div_t d{ 1, 2 };
  pair<int, int> e{ 1, 2 };
  fstream f{ "nul", ios::binary };
 
  // ここでは、以下のような丸括弧での初期化ができない。
  // pair<int, int> e2(1, 2);
  // fstream f2("nul", ios::binary);
};

一時オブジェクト

3つ目は一時オブジェクトです。配列や集成体の初期化が可能になったのが新しいです。

#include <fstream>
#include <vector>
#include <cstdlib>
#include <utility>
 
using namespace std;
 
using i3 = int[3];
 
int main()
{
 
  int{1};
  i3{ 1, 2, 3 }; // int[3]{ 1, 2, 3 }は構文エラー
  vector<int>{ 1, 2, 3 };
  div_t{ 1, 2 };
  pair<int, int>{ 1, 2 };
  fstream{ "nul", ios::binary };
}

new式

4つ目はnew式です。これも配列・集成体の初期化ができるようになっています。

#include <fstream>
#include <vector>
#include <cstdlib>
#include <utility>
 
using namespace std;
 
int main()
{
  // new式
  new int{ 1 };
  new int[3]{ 1, 2, 3 }; // 要素数の指定が必要
  new vector<int>{ 1, 2, 3 };
  new div_t{ 1, 2 };
  new pair<int, int>{ 1, 2 };
  new fstream{ "nul", ios::binary };
}

こうしてみると、配列で要素数の指定が必要なのが惜しいです。試しにnew int[] { 1, 2, 3 }と書いたらコンパイルエラーでした。

クラスのコンストラクタでの初期化

5つ目、コンストラクタでの初期化です。またまたですが、配列・集成体の初期化ができるようになっています。

#include <fstream>
#include <vector>
#include <cstdlib>
#include <utility>
 
using namespace std;
 
class C
{
  int a;
  int b[3];
  vector<int> c;
  div_t d;
  pair<int, int> e;
  fstream f;
 
  C() :
    a{ 1 },
    b{ 1, 2, 3 },
    c{ 1, 2, 3 },
    d{ 1, 2 },
    e{ 1, 2 },
    f{ "nul", ios::binary }
  {
  }
};

関数の実引数・添え字演算子

6つ目は実引数です。普通の関数の実引数のほか、添え字演算子が多重定義されている場合にも当てはまります。

#include <fstream>
#include <vector>
#include <cstdlib>
#include <utility>
 
using namespace std;
 
void fa(int);
void fb(const i3&);
void fc(const vector<int>&);
void fd(const div_t&);
void fe(const pair<int, int>&);
void ff(const fstream&);
 
struct CA{ char operator[](int); };
struct CB{ char operator[](const i3&); };
struct CC{ char operator[](const vector<int>&); };
struct CD{ char operator[](const div_t&); };
struct CE{ char operator[](const pair<int, int>&); };
struct CF{ char operator[](const fstream&); };
 
int main()
{
  fa({ 1 });
  fb({ 1, 2, 3 });
  fc({ 1, 2, 3 });
  fd({ 1, 2 });
  fe({ 1, 2 });
  // コンストラクタがexplicitの場合はダメ。
  // ff({ "nul", ios::binary });
 
  ca[{ 1 }];
  cb[{ 1, 2, 3 }];
  cc[{ 1, 2, 3 }];
  cd[{ 1, 2 }];
  ce[{ 1, 2 }];
  // コンストラクタがexplicitの場合はダメ。
  //cf[{ "nul", ios::binary }];
}

このコードでは、仮引数の型をconstなlvalue参照(const T&)にしていますが、rvalue参照(T&&)や値(T)でも大丈夫です。

return文

最後はreturn文です。これも、関数の実引数と同じく、新しい場合です。

#include <fstream>
#include <vector>
#include <cstdlib>
#include <utility>
 
using namespace std;
 
int fa()
{
  return{ 1 };
}
 
// 配列を返す関数はダメ。
//i3 fb() { return{ 1, 2, 3 }; }
const i3& fb()
{
  return{ 1, 2, 3 }; // 一時オブジェクトへの参照を返すので、実行したらダメ
}
 
vector<int> fc()
{
  return{ 1, 2, 3 };
}
 
div_t fd()
{
  return{ 1, 2 };
}
 
pair<int, int> fe()
{
  return{ 1, 2 };
}
 
// explicitなコンストラクタはダメ。
//fstream fe()
//{
//  return { "nul", ios::binary };
//}

まとめ

{}による初期化が本当にありとあらゆる場面で使えるということを示したく、それぞれの場合をつらつらと書き並べました。

変数定義だけでなく、代入やnew、引数、return、なんでも初期化できるようになりました。困った制限がなくなったと言えます。

今まで、= { … }と書きたいがために、その場限りの変数を作る(その後それをreturnしたり、代入やstd::copyしたりする)というコードをときどき書くことがありました。ですが、その必要が無くなり、私としては助かっています。


スポンサード リンク

この記事のカテゴリ

  • ⇒ 統一初期化・リスト初期化の見本帳