GOGATSU やぶ

【C++】【振り返り】備忘録の振り返り

 この2週間は組み込みのテストをしていた。

 今度からクラスのコーディングに戻る、多分。
 なので、2週間前に書いた平日の振り返りの振り返り。

 調べようと思っていたところ、理解したいところをチョイスして調べてみる。

1. クラスのメンバ変数でリソースを確保する場合に、留意したいこと

C++のクラスで動的メモリの確保を行うとき、二重解放に注意する。
C++では、newで動的にメモリを確保し、そのメモリのアドレスをポインタ変数に格納することができる。

 このとき、
 ・メモリはdeleteで解放すること
 ・コピーコンストラクタ、代入演算子について定義すること
について、考慮する。

 もしコピーコンストラクタと代入演算子を定義しなければ、デフォルトで定義されているものが呼び出される。
 その場合、メンバに持つポインタ変数も一緒にコピー、あるいは代入されてしまう。

 以下のプログラムでは、Dogクラスが名前を格納するためのポインタ変数m_nameを持っている。

class Dog
{
public:
    explicit Dog(int size):
    m_name(new char[size])
    {
    }
    ~Dog()
    {
        delete [] m_name;
    }

private:
    char* m_name;
};


int main () 
{
    Dog shiba(50);
    Dog pagu(40);
    pagu = shiba;
}

//実行エラー
//malloc: *** error for object 0x60000******: pointer being freed was not allocated
//メモリ割り当てされていない領域を解放することはできません。

 このクラスから生成されたshibaは、自分の名前を格納するためのポインタ変数を持っている。
 次に生成されたpaguも、同じポインタ変数を持っている。
 つまり二匹とも、同じ場所のメモリを持ってしまっている。

f:id:ybsatsuki:20211128191936p:plain

 関数から抜けると、shibaとpaguのデストラクタがそれぞれ動くので、確保したメモリに対して2回deleteが実行され、二重解放になる。

 やってみると、コンパイルエラーにはならず、実行エラーになった。

---コピーコンストラクタと代入演算子の定義について 参考:---

■書籍「API design for C++

C++ のコピーコンストラクターと代入演算子 | プログラマーズ雑記帳

【C++】メンバにポインタを持つクラスの注意点(2重解放、コピーコンストラクタ、代入演算子) - Qiita

--------------------------------------------------------------

2. ムーブコンストラク

 オブジェクトの中身を、そっくりそのまま別オブジェクトに移動させることができる。

---参照---

ムーブコンストラクタ|ムーブセマンティクスやコンテナ高速化との関係 | MaryCore

・またムーブコンストラクタは元オブジェクトのコピーではなく移動を実現するため、元オブジェクトのメンバは移動(p(v.p))した後にゼロ化(v.p = nullptr)する必要がある。  

・ムーブコンストラクタを呼び出すためには、コンストラクタ引数に対して右辺値参照(&&)を渡す必要がある。
 そのためにはstd::move()やstatic_cast<A&&>()によるキャスト処理でもって右辺値参照を明示する必要がある。

・なお譲渡の際には、元のオブジェクトのその後振る舞いによってムーブ先のオブジェクトに影響が及ばないようにしなければならない。

----------

3. 関数のdelete宣言

 例えば、上記1のコードのように、ある値にクラスのオブジェクトを代入するような処理は書いて欲しくない場合、関数のdelete宣言によって防ぐことができる。

class Dog
{
public:
    explicit Dog(int size):
    m_name(new char[size])
    {
    }
    ~Dog()
    {
        delete [] m_name;
    }

    Dog& operator=(const Dog&) = delete; //この関数は削除(オブジェクトの代入禁止)

private:
    char* m_name;
};


int main () 
{
    Dog shiba(50);
    Dog pagu(40);
    pagu = shiba; //エラー:Dog& operator=(const Dog&) は参照できません。削除された関数です。
}

型名をメンバ変数で定義する

型名が長すぎる場合。
(既存ライブラリの引数や戻り値の型が長いけど、その型を使わなければいけないとき)

---参考---

エイリアスと typedef (C++) | Microsoft Docs

----------