クラス

宣言

class ClassName;

他のクラスの定義などで、その名前だけが必要なときには宣言のみをします。

定義

[template-spec] class [ms-decl-spec] [tag [: base-list ]]
{
    member-list
} [declarators];
class (C++) | MSDN

アクセス制御 (Access controls)

修飾子  
private private (C++) | MSDN
protected protected (C++) | MSDN
public public (C++) | MSDN
メンバー アクセス コントロール (C++) | MSDN

匿名クラス (Anonymous Class)

識別子を付けずに定義することで匿名にできます。これは、Cとの互換性のための機能です。匿名クラス型 | MSDN

typedef struct
{
    unsigned x;
    unsigned y;
} POINT;

匿名構造体

継承 (Inheritance)

class Derived : [virtual] [access-specifier] Base1,
                [virtual] [access-specifier] Base2, ...
{
    // member list
};

access-specifierは、基底クラス (基本クラス) のメンバに対して適用されるアクセス許可で、省略した場合はprivateとみなされます。派生クラスのアクセス コントロール - メンバー アクセス コントロール (C++) | MSDN

複数の基底クラスを指定した場合は、多重継承 (Multiple Inheritance) と呼ばれます。多重継承 | MSDN

class BaseClass
{
};

class MyClass : public BaseClass
{
};

抽象クラス (Abstract Classes)

純粋仮想関数を1つでも含むクラスは抽象クラスであり、派生元となれるだけで、このクラスのオブジェクトを作成することはできません。抽象クラス (C++) | MSDN

コンストラクタ (Constructors)

class MyClass
{
public:
    MyClass();
};

初期化リスト (Initializer List) / イニシャライザ

メンバ変数を初期化するための機能です。メンバ変数はコンストラクタの実行前に初期化されることになっているため、これを用いずコンストラクタ内で代入すると初期化された値に上書きすることになります。

class MyClass
{
    int a;
    int b;

public:
    MyClass( int x, int y ) : b( y ), a( x ) // 初期化リスト
    {
        // a = x; とすると初期化ではなく代入となる
        // 初期化リストはb,aの順だが、宣言された順に基づきa,bの順で初期化される
    }
};

初期化リストはその変数が宣言された順番で実行されるため、この例ではaの次にbが初期化されます。

初期化リストは、派生クラスのコピーコンストラクタにおいて重要です。これで明示的に基底クラスのコピーコンストラクタを呼ばないと、基底クラスのデフォルト コンストラクタが呼ばれてしまい、結果としてメンバがコピーされなくなります。

class BaseClass
{
    int a;
public:
    BaseClass( int x ) : a( x ) // 初期化リスト
    {
    }
};

class SubClass : public BaseClass
{
public:
    SubClass( int x ) : BaseClass( x ) // 初期化リスト
    {
    }
};

コピー コンストラクタ (Copy Constructors)

class MyClass
{
    int a;
    int b;

public:
    MyClass( const MyClass& obj ) :a( obj.a ), b( obj.b )
    {
    }
};
コピー コンストラクターとコピー代入演算子 (C++) | MSDN

コンストラクタのオーバーロード

多重定義された他のコンストラクタは、初期化リストから呼べます。

class MyClass
{
public:
    int a;

    MyClass(int x)
    {
        a = x;
    }

    MyClass() : MyClass(5) // 多重定義されたコンストラクタが先に呼ばれる
    {
        // ここでMyClass(5) としても、ここで新たなオブジェクトが生成されるだけ
    }
};

int main()
{
    MyClass myClass(1); // myClass.aは 1となる
    MyClass myClass;    // myClass.aは 5となる
}

複雑な処理は、共通の初期化用の関数を呼ぶようにします。既定の引数とヘルパ関数 (C++) | MSDN

class MyClass
{
private:
    Init(int a)
    {
    }

public:
    MyClass()
    {
        Init(0)
    }

    MyClass(int a)
    {
        Init(a)
    }
};

デストラクタ (Destructors)

class MyClass
{
public:
    ~MyClass();
};

デストラクタは、次のいずれかの瞬間に呼び出されます。デストラクターの使用 - デストラクター (C++) | MSDN

  • new演算子で作成されたオブジェクトに対して、delete演算子が呼ばれたとき
  • デストラクタが明示的に呼ばれたとき
  • ローカルで作成されたオブジェクトが、そのスコープから外れたとき
  • オブジェクトが存在する状態で、プログラムが終了するとき

メンバ関数 (Member Functions)

class MyClass
{
    void Func(); // 宣言
};

// 定義
void MyClass::Func()
{
}

関数のオーバーロード (Function Overloading) / 多重定義

関数のオーバーロードとは、クラス内に同名で引数や戻り値が異なるメンバ関数を複数定義することです。

class MyClass
{
    int Func( int a );
    int Func( double a );
};

メンバ関数は名前と引数で識別されるため、戻り値の型だけが異なる関数は定義できません。

class MyClass
{
    int    Func( int a );
    double Func( int a ); // エラー
};

演算子のオーバーロード (Operator Overloading)

組み込みの演算と同じような操作を、クラスに実装できます。

type operator operator-symbol ( parameter-list )
演算子のオーバーロード | MSDN
変換演算子 (conversion operators) / キャスト演算子 (cast operators)
operator double() const { return value; }
ユーザー定義型変換 | MSDN

明示的な型変換を強制するならば、explicitを指定します。

explicit operator double() const { return value; }

仮想関数 (Virtual Functions)

仮想関数とは、動的に呼び出す関数が決定されるメンバ関数です。virtualキーワードを指定して宣言することで、仮想関数となります。

class MyClass
{
    virtual void Func();
};

継承したクラスでこの仮想関数を実装することで、ポリモーフィズム (Polymorphism / 多態性) を実現できます。

関数のオーバーライド (Function Overriding)

関数のオーバーライドとは、基底クラスの仮想関数より派生クラスのそれを優先する (override) ことです。

純粋仮想関数 (Pure Virtual Functions)

純粋仮想関数とは、定義を持たない仮想関数です。仮想関数に、=0という初期設定子を付加することで宣言できます。

class MyClass
{
    virtual void Func() = 0;
};

参考

参考書

定数メンバ関数 (const member functions)

関数宣言にconstを付加することで、その関数ではメンバ変数を変更しないことを表明できます。const のメンバー関数 - const (C++) | MSDN

class MyClass
{
    int a = 0;

    void Func1() const; // 定数メンバ関数 の宣言
    void Func2();
};

void MyClass::Func1() const // 定数メンバ関数 の定義
{
    a++;     // error C3490: 'a' は const オブジェクトを通じてアクセスされているため変更できません
    Func2(); // error C2662: 'void MyClass::Func2(void)': 'const MyClass' から 'MyClass &' へ 'this' ポインターを変換できません。

    int x = 0;
    x++; // メンバ変数ではないから、ok
}

void MyClass::Func2()
{
    a++; // ok
    Func1(); // ok
}

静的メンバ (Static Members)

staticを付加して宣言することで、静的メンバとなります。

class MyClass
{
public:
    // 宣言
    static int x;       // 静的メンバ変数 (クラス変数)
    static void Func(); // 静的メンバ関数 (クラス関数)
};

// 定義
int MyClass::x = 1;
void MyClass::Func()
{
}

静的なメンバであることは宣言で指定し、定義では不要です。

static int MyClass::x;        // error C2720: 'MyClass::x': 'static ' ストレージ クラスの指定子が識別子に対して誤って指定されています。
static void MyClass::Func() {} // error C2724: 'MyClass::Func': 'static' をメンバー関数の定義に使ってはいけません。

静的なメンバ変数の初期化

class MyClass
{
public:
    static int x;

    static const int    y = 2;   // 定数の整数のみクラス内で初期化できる
    static const double y = 2.0; // エラー コンパイラ エラー C2864 | MSDN
};

int MyClass::x = 10;

オブジェクトの生成と破棄

void Func()
{
    MyClass myClass; // 生成

    myClass.x++;

    // ブロックから出るときに破棄される
}
void Func()
{
    MyClass* myClass = new MyClass(); // 生成

    myClass->x++;

    ...

    delete myClass; // 明示的に破棄する
}
new で割り当てたオブジェクトの初期化 - new 演算子 (C++) | MSDN

new

[::] new [placement] new-type-name [new-initializer]
[::] new [placement] ( type-name ) [new-initializer]
new 演算子 (C++) | MSDN
void *__cdecl operator new(size_t count);
new 演算子 (CRT) | MSDN
void *__cdecl operator new[](size_t count);
演算子 new(CRT) | MSDN

delete

[::] delete cast-expression
[::] delete [ ] cast-expression
delete 演算子 (C++) | MSDN

オブジェクトが配列を参照している場合には、delete []とします。これをdeleteとすると、予期せぬ結果をもたらします。

int* p = new int[10];

...

delete [] p;

問題の検出

アドレスのずれ
MyClass* myClass = new MyClass();
myClass++;

delete myClass;
---------------------------
Microsoft Visual C++ Runtime Library
---------------------------
Debug Assertion Failed!

Program: ***.exe
File: minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp
Line: 888

Expression: _CrtIsValidHeapPointer(block)
配列の境界位置への書き込み
int* p = new int[3];
p[3] = 1;

CRTのデバッグ ヒープ関数では、次のように検出されます。

---------------------------
Microsoft Visual C++ Runtime Library
---------------------------
Debug Error!

Program: ***.exe

HEAP CORRUPTION DETECTED: after Normal block (#**) at 0x***.
CRT detected that the application wrote to memory after end of heap buffer.

境界を越えた位置、たとえばこの例ではp[4]p[-2]の位置への書き込みは、このようには検出されません。

テスト環境でデバッグしたとき、次のように検出されることもあります。

---------------------------
Microsoft Visual C++ Runtime Library
---------------------------
Debug Assertion Failed!

Program: ...XTENSIONS\MICROSOFT\TESTWINDOW\vstest.executionengine.x86.exe
File: minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp
Line: 892

Expression: is_block_type_valid(header->_block_use)

また0xc0000374で終了するときも、ヒープの破壊が原因のことがあります。ヒープコラプションのデバッグ手順 ~ 例外 STATUS_HEAP_CORRUPTION (0xc0000374) - Web/DB プログラミング徹底解説

プログラム '[***] ***.exe' はコード -1073740940 (0xc0000374) で終了しました。

参考

参考書

Microsoft Learnから検索