[C++] コンストラクタの書き方


# そもそもコンストラクタは必要か？

[集積体初期化](https://ja.cppreference.com/w/cpp/language/aggregate_initialization)を使えるなら、あえてコンストラクタを書かない選択肢もあります。

```cpp
struct Foo final
{
  int         m_val_1 {} ;
  double      m_val_2 {} ;
  std::string m_val_3 {} ;
};
```
```cpp
const Foo obj{ 1, 1.5, "a" };
```

冗長な記述が不要となるのが、メリット。

ただし、デメリットもあります。
* 使用するのに、条件が有る
  * 全てのメンバ変数が public である事、など
* 引数の数が足りなくてもビルドが通る
```cpp
const Foo obj{ 1, 1.5 }; // 数が合わないが、ビルドは通る。
```


# 基本的な書き方

```cpp
struct Foo final
{
public:
  explicit Foo( const int val )
    : m_val{ val }
  {}
private:
  int m_val {} ;
};
```

`explicit` は、常に付ける様にした方が良いかと思います。
* 引数が1つの時に、不意に変換コンストラクタとして機能するのを防ぐため。
  * 引数が1つの時のみ付けるだと、注意を払うべき事象が無駄に増えてしまう。
<!-- * コンストラクタで初期化を行う場合でも、変数宣言時の初期化を書いておく。
  * 拡張,変更時のケアレスミスに備えて -->


なおに、メンバ変数と仮引数が同名でも構いません。

```cpp
struct Foo final
{
public:
  explicit Foo( const int val )
    : val{ val } // 左側(波括弧の外)は関数のスコープ外なので、メンバ変数を指す。
                 // 右側(波括弧の中)は関数のスコープ内なので、関数の引数を指す。
  {}
private:
  int val {} ;
};
```

# move でコピーコストを減らす

コピーコストが重いメンバ変数を保つ場合、
右辺値参照を取るコンストラクタを用意する事でコピーコストが減らせます。

```cpp
struct Foo final
{
public:
  explicit Foo( const std::vector<int>& val )
    : m_val{ val }
  {}

  explicit Foo( std::vector<int>&& val )
    : m_val{ std::move( val ) }
  {}
private:
  std::vector<int> m_val;
};
```
```cpp
const std::vector<int> ary = GetAry() ;
Foo obj_1{ ary      } ; // vector はコピーされる。
Foo obj_2{ GetAry() } ; // vector はムーブされる。
```

ただし、メンバ変数1つに付き2倍の数のコンストラクタが必要になるので、メンテが大変になります。

ムーブのコストが軽いのなら、引数を値渡しにした方が良いでしょう。
```cpp
struct Foo final
{
public:
  explicit Foo( std::vector<int> val )
    : m_val{ std::move( val ) }
  {}
private:
  std::vector<int> m_val;
};
```
```cpp
const std::vector<int> ary = GetAry() ;
Foo obj_1{ ary      } ; // vector はコピーされた後、ムーブされる。
Foo obj_2{ GetAry() } ; // vector はムーブされた後、ムーブされる。
```

# const参照を持つ場合

const参照なメンバ変数を持つ場合、簡単には以下の様にする。

```cpp
struct RefKeeper
{
  explicit RefKeeper( const double& val )
    :m_ref{ val }
  {}

  const double& m_ref;
};
```

が、これだと、コンストラクタで右辺値も取れてしまいます。

```cpp
RefKeeper obj{ 7.53 };
obj.m_ref; // ダングリング(i.e.寿命切れ)
```

なので、右辺値を取るオーバーロードを用意し、
`delete` することで、これを防げます。
```cpp
struct RefKeeper
{
  explicit RefKeeper( const double& val )
    :m_ref{ val }
  {}
  explicit RefKeeper( const double&& val ) = delete ; // 右辺値禁止！

  const double& m_ref;
};
```

# 条件チェックを行う

コンストラクタで事前条件のチェックを行いたい時、
例外を投げるのは、色々な面で扱いづらいです。
* 投げる例外を決める必要がある, チェック漏れされやすい, 値を`const`にしづらい、など。

この場合、コンストラクタで `private` にし、チェック付きの `static` 関数でラップする事で、
`optional` にして返す事が出来る様になります。

```cpp
#include <optional>

// どんな時でも、ポジティブバリュー
struct PositiveValue
{
public:
  static std::optional<PositiveValue> Create( const int val ) noexcept {
    if ( val <= 0 ) { return std::nullopt ;}
    return PositiveValue{ val };
  }
private:
  explicit PositiveValue( const int val )
    : m_val{ val }
  {}
private:
  int m_val {};
};
```

```cpp
const auto obj_1 = PositiveValue::Create( 5 ) ;
const auto obj_2 = PositiveValue::Create( 0 ) ; // nullになる。
```


<!-- # 他のメンバ変数を初期化に使う -->
# メンバ変数の複雑な初期化

以下の様な、コンストラクタで平均と分散を計算するクラスを考える。
* 分散の計算には、平均が必要な事に留意。

```cpp
struct Foo final
{
public:
  explicit Foo( const std::vector<int>& ary )
    : m_average { ... }, m_variance{ ... } // コンストラクタで、引数 ary の平均,分散を計算する。
  {}
private:
  double m_average {}; // 平均
  double m_variance{}; // 分散
};
```

以下の様に、メンバの初期化で他のメンバを使う事も出来ますが、
メンバ変数の宣言順を変えると未初期化の変数を使ってしまう恐れがあります。
```cpp
struct Foo final
{
public:
  explicit Foo( const std::vector<int>& ary )
    : m_average { CalcAverage( ary ) }
    , m_variance{ CalcVariance( ary, m_average ) } // メンバ変数の宣言順に注意！
  {}
private:
  double m_average {}; // 平均
  double m_variance{}; // 分散
};
```

以下の様に内部クラスを別途用意すると、複雑な計算を伴う初期化が行いやすくなります。
```cpp
// 宣言
struct Foo final
{
public:
  explicit Foo( const std::vector<int>& ary ) ;
public:
  struct Data final
  {
    double average {}; // 平均
    double variance{}; // 分散
  } ;
private:
  Data m{};
};

// 実装
Foo::Data Init( const std::vector<int>& ary )
{
  const auto average  = CalcAverage( ary ) ;
  const auto variance = CalcVariance( ary, average ) ;
  return { average, variance };
}

Foo::Foo( const std::vector<int>& ary )
  : m{ Init( ary ) }
{}
```

# 継承が有る場合に集積体初期化を使う(C++14)

[C++17 で改善された](https://cpprefjp.github.io/lang/cpp17/extension_to_aggregate_initialization.html) が、何らかのインターフェースを継承していると、
集積体初期化は利用出来なくなってしまいます。

```cpp
struct Foo : public IF
{
  int         m_val_1 {} ;
  double      m_val_2 {} ;
  std::string m_val_3 {} ;
};
```
```cpp
// Foo obj_1{ 1, 1.5, "foo" }; // ビルドエラー。
Foo obj_2{ {}, 1, 1.5, "foo" }; // C++17なら可,C++14ではビルドエラー。
```

内部クラス等でラップすれば、C++14でも集積体構築が利用出来ます。
```cpp
struct Foo : public IF
{
public:
  struct Data
  {
    int         val_1 {} ;
    double      val_2 {} ;
    std::string val_3 {} ;
  };
  explicit Foo( const Data& m ) : m{ m } {}
private:
  Data m{};
};
```
```cpp
Foo obj_1{ Foo::Data{ 1, 1.5, "foo" } }; // C++14でも可
Foo obj_2{          { 1, 1.5, "foo" } }; // 型名は省略可
```