C++躓き(std::unique_ptr)

C2280エラー

以下のコードを実行するとC2280エラーが発生した。

class Clz
{
private:
  std::unique_ptr<int> m_n{nullptr};
public:
  Clz(){}
  ~Clz(){}
}

int main()
{
  std::vector<Clz> clzs;
  Clz clz{};
  clzs.push_back(std::move(clz)); // コンパイルエラー
  clzs.emplace_back(); // コンパイルエラー
}

コピーコンストラク

std::unique_ptrはコピーできないメンバ変数のため、コピーコンストラクタは暗黙的に削除される。

class Clz
{
public:
  Clz(const Clz& v) = delete;
}

ムーブコンストラク

デストラクタを明示的に定義しているため、暗黙的に削除される。

vector

push_back

要素をコピーして渡す。⇒コピーコンストラクタを呼び出すためコンパイルエラー

emplace_back

直接構築で新たな要素を末尾に追加する。
メモリ領域にはムーブもしくはコピーが行われる。
上記の場合、ムーブコンストラクタが削除されるため、コピーコンストラクタが呼び出される。

std::move

ムーブできるようにキャストする。ムーブできな場合はコピーコンストラクタが呼び出される。

メモリの確保

静的/動的メモリの割り当て

静的メモリの割り当て

確保する領域が固定長。コンパイル時に割り当てられる。
メンバ変数にある場合はオブジェクトの生成時に一緒に作成される。

動的メモリの割り当て

プログラム実行中にメモリが割り当てられる。
メンバ変数にある場合は、生成を遅らせることができる。

クラスのメンバ初期化

一様初期化(uniform initialization)

以下のように、波括弧を使って初期化を行う。
統一初期化とも呼ばれる。
一様初期化子を使用した場合は、変数がゼロ値になることが保証される。

int i1 = 1;
int i2(1);
int i3{1};
class T
{   
private:
  int m_num{10};
  std::string m_str{"hoge"};
  
public:
  T() : m_num{10} {}
}

上記の場合、m_numは10、m_strは"hoge"となる

スマートポインタ

スマートポインタ

動的割り当てした場合、解放しなければならない。
これを正しく行うのは難しい。

void func(){
  auto p1 = new int(1);
  auto p2 = new int(2); // ★
  
  delete p2;
  delete p1;
}

★で例外が発生した場合、後続のdelete3は実行されない。

unique_ptr

解放とムーブを行ってくれるライブラリ。
オブジェクトが複数存在することを防ぐためコピーはできない。

関数の引数

  • 所有権を渡したいときはstd::unique_ptr
  • それ以外は生ポインタ

関数の戻り値

 

  • ファクトリ関数の戻り値は原則std::unique_ptr

 

std::unique_ptr<T> CreateT();

クラスのメンバ変数

  • 所有権を持っているならstd::unique_ptr
  • 所有権を持っていないなら、生のポインタ

代入演算子

コピー代入演算子

引数はコピー元オブジェクトの左辺値参照。
戻り値はコピー先オブジェクトの左辺値参照。

class T{
  T& operator=(const T& t);
}

コピー代入演算子を定義していない場合、以下に該当しなければ、コンパイラが暗黙的に作成する。

  • コピー代入演算子が定義されていないデータメンバを持つ
  • ムーブコンストラクタ、ムーブ代入演算子を定義

ムーブ代入演算子

引数はムーブ元オブジェクトの右辺値参照。
戻り値はムーブ先オブジェクトの左辺値参照。

class T{
  T& operator=(T&& t);
}

ムーブ代入演算子を定義していない場合、以下に該当しなければ、コンパイラが暗黙的に作成する。

  • ムーブ代入演算子が定義されていないデータメンバを持つ
  • コピーコンストラクタ、コピー代入演算子、ムーブコンストラクタ、デストラクタを定義

コンストラクタ

コンストラクタ(Constructor)

オブジェクト作成の際に、メンバ変数の初期化を主目的に呼び出されるメンバ関数

デフォルトコンストラク

引数が1つも指定されていないコンストラクタ。
コンストラクタを明示的に1つも定義していない場合は、コンパイラが暗黙的に作成する。

変換コンストラク

引数が1つのコンストラクタ。
変換コンストラクタを定義すると、暗黙的なコンストラクタ呼び出しによる初期化が行われる。

class T {
 T(int num){}
}

int main() {
  T t1(10);
  T t2 = 10; // T t2(10);が暗黙的に呼び出される。
}

explicit

変換コンストラクタに指定することにより、暗黙的なコンストラクタ呼び出しによる初期化を禁止する。

class T {
 explicit T(int num){}
}

int main() {
  T t1(10);
  //T t2 = 10; // error
}

コピーコンストラク

同じクラスの参照を受け取るコンストラクタ(変換コンストラクタ)。
受け取ったオブジェクトを使用して、メンバ変数の初期化を行う。

class T {
 T(const T& t){} // コピー元を変更せず(const)に初期化を行う。
}

コピーコンストラクタは以下のタイミングで呼びされる

  • 等号を使用した初期化
  • オブジェクトを関数に渡す(値渡し)
  • 関数からオブジェクトを返す

コピーコンストラクタを定義していない場合、以下に該当しなければ、コンパイラが暗黙的に作成する。

  • コピーできない、右辺値参照型のメンバ変数を持つ
  • ムーブコンストラクタ、ムーブ代入演算子を定義

ムーブコンストラク

同じクラスの右辺値参照を受け取るコンストラクタ(変換コンストラクタ)。
受け取ったオブジェクトを移動/譲渡してメンバ変数の初期化を行う。

class T {
 T(T&& t){} 
}

ムーブコンストラクタを定義していない場合、以下に該当しなければ、コンパイラが暗黙的に作成する。

  • ムーブできないメンバ変数を持つ
  • コピーコンストラクタ、コピー代入演算子、ムーブ代入演算子、デストラクタを定義

ポインタ

概要

コンピュータはメモリがすべてで、ポインタはこのメモリの管理と操作を行うのに重要。

T * で"既定のT型に後ろから就職して新しいTのポインタ型を作る"。

メモリ

メモリは1直線の道のようなもので、そこに家がある。

家にはアドレスが必要で、このメモリアドレス(数値)を格納するのがポインタ。

家には様々な大きさがあり、このサイズはTに依存する。

宣言

int *pointer;

nullptr

上記は何も参照していない状態。これに対して読み書きを行うと未定義の挙動を行う。

何も参照していないことを明示的に示すためのポインタの値がnullptr