C++の基礎 - クラス

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
2022年12月3日 (土) 23:48時点におけるWiki (トーク | 投稿記録)による版 (→‎実体化の禁止)
ナビゲーションに移動 検索に移動

概要



staticメンバ

staticメンバ変数

クラスのメンバ変数にstaticを付加することにより、staticメンバ変数となる。

staticメンバ変数は、クラスの定義に記述しただけでは定義したことにならないため、実体となる定義をクラス定義と同じスコープに記述する必要がある。

 class CSampleClass
 {
    static 型名 メンバ変数名;  // 宣言
 };
 
 型名 CSampleClass::メンバ変数名;  // 定義


staticメンバ変数は、オブジェクトを生成せずにプログラムの開始時に既に存在しているため、自動的にゼロで初期化される。
ただし、staticメンバ変数の型が、constな整数型、または、constenum型の場合に限り、宣言と同時に初期化子を与えることができる。

 class CSampleClass
 {
 private:
    enum E { e1, e2 };
 
    static const int ci = 100;     // OK
    static const E ce = e1;        // OK
    static const double cf = 1.0;  // コンパイルエラー
    static int i = 100;            // コンパイルエラー
    static E e = e1;               // コンパイルエラー
 };


コンパイルエラーになるパターンでは、定義時に初期化子を与える必要がある。

 class CSampleClass
 {
 private:
    enum E
    {
       e1,
       e2
    };
 
    static const int ci = 100;         // OK
    static const E ce = e1;            // OK
    static const double cf;
    static int i;
    static E e;
 };
 
 const double CSampleClass::cf = 1.0;                 // OK
 int CSampleClass::i = 100;                           // OK
 CSampleClass::E CSampleClass::e = CSampleClass::e1;  // OK


非staticのメンバ変数が、オブジェクト1つごとに別個に存在することに対して、staticメンバ変数は、クラスに対して1つだけ存在する。
オブジェクトが複数存在しても、各オブジェクトからただ1つの変数を共有する。

このような特徴を持つため、staticメンバ変数は、各オブジェクトが必要とする共通情報を管理することに利用できる。

以下の例では、インスタンス化したオブジェクトの総数を管理している。

 // CSampleClass.h
 
 #ifndef CSAMPLECLASS_H
 #define CSAMPLECLASS_H
 
 class MyClass
 {
 public:
    CSampleClass();
    ~CSampleClass();
 
 private:
    static int msObjectCount;  // staticメンバ変数の宣言
 };
 
 #endif


 // CSampleClass.cpp
 
 #include "CSampleClass.h"
 
 // staticメンバ変数の実体
 int MyClass::msObjectCount;
 
 CSampleClass::CSampleClass()
 {
    msObjectCount++;
 }
 
 CSampleClass::~CSampleClass()
 {
    msObjectCount--;
 }


クラスの外部から、staticメンバ変数にアクセスする場合は、クラス名::staticメンバ変数名という形で、スコープ解決演算子を使用して記述する。
また、一般的ではないが、非staticなメンバ変数と同様に、<オブジェクト名>.<staticメンバ変数名>のようなアクセスも可能である。

一般的には、staticメンバ変数は非公開である方がよいが、const staticを付加している場合は、公開しても問題ない。

クラスの定義内やメンバ関数の定義内から、自身のクラスのstaticメンバ変数へアクセスする場合は、staticメンバ変数名のみを使用して記述することができる。

staticメンバ関数

クラスのメンバ関数にもstatic指定子を付加することができる。(staticメンバ関数)

 class <クラス名>
 {
    static <戻り値の型> <メンバ関数名>(<引数>);
 };
 
 <戻り値の型> <クラス名>::<メンバ関数名>(<引数>)
 {
    // ...略
 }


staticメンバ関数にconstを付加することはできない。
また、staticメンバ関数をインライン関数にすることも可能である。

staticメンバ関数のオーバーロードも可能であるが、staticメンバ関数と非staticメンバ関数との間において、同じ名前を使用することはできない。

staticメンバ変数と同様に、staticメンバ関数もクラスに属するため、staticメンバ関数はオブジェクトを生成せずに呼び出すことができる。

<クラス名>::<メンバ関数名>(<引数>)


オブジェクト(実体)から呼び出さないため、staticメンバ関数内では、thisポインタを使用することはできない。
また、一般的ではないが、<オブジェクト名>.<staticメンバ関数名>(<引数>)の形で呼び出すことも可能である。

非staticなメンバ関数から、staticメンバ関数を呼び出すことはできるが、staticメンバ関数から、非staticのメンバ変数にアクセスできない。

publicなstaticメンバ関数は、通常の関数と同様に扱うことができるが、
クラスに含まれていることにより、スコープを限定できること、staticメンバ変数という専用のデータの置き場が使用できることが特徴である。

 #ifndef CSAMPLECLASS_H
 #define CSAMPLECLASS_H
 
 class CSampleClass
 {
 public:
    CSampleClass()
    {
       msObjectCount++;
    }
 
    ~CSampleClass()
    {
       msObjectCount--;
    }
 
    static int GetObjectCount()
    {
       return msObjectCount;
    }
 
 private:
    static int msObjectCount;  // staticメンバ変数の宣言
 };
 
 // staticメンバ変数の実体
 int CSampleClass::msObjectCount = 0;
 
 #endif


 // main.cpp
 
 #include <iostream>
 #include "CSampleClass.h"
 
 int main()
 {
    std::cout << CSampleClass::GetObjectCount() << std::endl;  // msObjectCount : 0

    CSampleClass  c1;
    CSampleClass *c2 = new CSampleClass();
    std::cout << CSampleClass::GetObjectCount() << std::endl;  // msObjectCount : 2
    delete c2;
 
    std::cout << CSampleClass::GetObjectCount() << std::endl;  // msObjectCount : 1
 }



staticオブジェクト

staticクラスは、自動的にゼロで初期化された後、staticクラスが宣言された箇所が初めて実行される時にコンストラクタが呼び出される。

staticで宣言したクラスの場合、任意の関数が終了してもデストラクタは呼び出されない。
staticクラスのデストラクタは、ソフトウェア終了直前に呼び出される。

 void func()
 {
    static CSampleClass clsSample;
 }
 // オブジェクトclsSampleは削除されないため、CSampleClassクラスのデストラクタは呼び出されない



staticクラス

C++ 03以前

全てのメンバがstaticメンバであるクラスを、staticクラス(静的クラス)と呼ぶ。

staticメンバはオブジェクトを生成せずに使用できるため、staticクラスはインスタンス化の必要性がないことが特徴である。
そのため、staticクラスを記述する場合、インスタンス化を禁止するように設計する。

C++でインスタンス化を禁止するには、コンストラクタをprivateにする。
なお、コンストラクタが呼び出される可能性は無いため、実装を記述するべきではない。

 class CSampleClass
 {
 private:
    CSampleClass();  // 唯一のコンストラクタがprivateの場合は、インスタンス化できない
 };


問題点として、staticクラスのメンバ関数内において、コンストラクタを使用することである。
これは、クラス内のメンバへのアクセスとなるため、privateであってもアクセス可能となり、コンパイルエラーにならない。

これを、コンパイルエラーとする場合、コンストラクタの実装を記述しないことにより、リンクエラーを起こす方法を採ればよい。
(コンパイルは通るが、関数の実装が無いというリンクエラーが起きる)

 void CSampleClass::func()
 {
    CSampleClass *c = new CSampleClass();
 }


staticクラスの価値は、任意の機能群を1つのスコープにまとめて記述できることである。
1つのヘッダファイルにstaticクラスの機能群をまとめて、<クラス名>::<メンバ>のようにスコープを使用してアクセスすることができる。

例えば、ファイルをコピーまたは削除する機能は、それぞれ1つの関数で完結できるため、非staticクラスではメンバ変数を持つ必要はない。
以下の例では、staticクラスを定義して、ファイルをコピーまたは削除する機能を記述している。

 class FileSystem
 {
 public:
    static void Copy(const char* src, const char* dest);
    static void Delete(const char* path);
 
 private:
    FileSystem();
 };


ただし、スコープを限定するという目的の場合、名前空間を使用する方がよい。
この場合、コンストラクタをprivateにする処理も不要である。

 namespace FileSystem
 {
    void Copy(const char* src, const char* dest);
    void Delete(const char* path);
 }
 
 FileSystem::Copy("sample.txt", "sample_cp.txt");
 FileSystem::Delete("sample.txt");


C++ 11以降

C++ 11以降では、デフォルトコンストラクタとデストラクタにdeleteキーワードを指定することにより、実体化を禁止することができる。
メンバにdeleteキーワードを指定する場合は、publicに記述することがセオリーである。

finalは、継承をさせないことを明示的に宣言、または、派生クラスでの仮想関数のオーバーライドをさせないことを明示的に宣言するキーワードである。

 // C++ 11以降
 
 class CSampleClass final
 {
 public:
    CSampleClass() = delete;
    ~CSampleClass() = delete;
 public:
    static int Add(int x, int y)
    {
       return x + y;
    }
 };


Visual C++では、abstractキーワードが使用できるため、abstract sealedまたはabstract finalを指定することにより、staticクラスを記述することができる。
ただし、その場合は、他のプラットフォームではそのまま移植できないことに注意が必要となる。

また、Visual C++では、コンパイルオプションに/permissive-を付加することにより、独自拡張を無効化することもできる。