C++の基礎 - フレンド

提供: MochiuWiki : SUSE, EC, PCB

2022年12月4日 (日) 18:06時点におけるWiki (トーク | 投稿記録)による版 (概要)

概要

privateメンバは、そのメンバを持つクラスからのみアクセスできるが、例外的に、特定の相手にのみprivateメンバへのアクセスを許可するフレンドがある。
フレンドは、特定の関数からのアクセスを許可するフレンド関数、および、特定のクラスからのアクセスを許可するフレンドクラスの2つが存在する。

フレンドについては賛否両論あり、使用すべきではないという意見もある。
実際に、privateメンバへアクセスできる経路を作るため、使いどころには注意が必要となる。

しかし、フレンドは相手を明確に限定しているため、特定の相手のためだけにprivateメンバをpublicメンバに変更するよりもよい方法だと言える。
フレンドを使用するかどうかを決定する前に、そのクラスのメンバにできないかどうかを考えるべきである。


フレンド関数

特定の関数に対してのみアクセスを許可するようなフレンドを、フレンド関数と呼ぶ。
通常の関数、メンバ関数(staticメンバ関数も含む)、コンストラクタ、デストラクタ、オーバーロードされた演算子等の全ての関数が該当する。
また、関数テンプレートも指定できる。

friendキーワードを使用して、フレンドにする関数を指定する。これを、フレンド宣言と呼ぶ。
フレンド宣言は、他の関数からもメンバにアクセスしていることを理解しやすくするため、クラス内の先頭に記述する方がよい。

フレンド関数から、非staticメンバにアクセスする場合、そのクラスのオブジェクトが必要となる。(どのオブジェクトのメンバにアクセスするかが不明なため)
そのため、フレンド関数に渡す実引数により、オブジェクトを指定する必要がある。

以下の例において、フレンド関数がCSampleClass&型の引数を持つ理由は、どのオブジェクトのメンバにアクセスするかを明示する必要があるからであるが、
staticメンバ関数にアクセスする場合は、これは不要である。

 class CSampleClass1
 {
    friend void func(CSampleClass1& x);
    friend void CSampleClass2::func(CSampleClass1& x);  // CSampleClass2の定義が必要となる

    // ...略
 };


また、フレンド関数にオーバーロードされた関数がある場合、フレンド関数にできる関数は、引数、戻り値、const等の全てが一致したものに限られる。

 int main()
 {
    CSampleClass1 cls1,
                  cls2;
    func(cls1);  // cls2ではなくcls1にアクセスする
 }


メンバ関数をフレンド指定する場合は、そのメンバ関数が所属するクラスの定義が見える必要がある。
また、フレンド宣言の記述は、相手先のアクセス指定の影響を受けるため、上記の例でいうと、CSampleClass2::func関数はpublicにする必要がある。

以下の例では、CSampleClass1の前にCSampleClass2が必要で、CSampleClass2の前にCSampleClass1が必要という相反する要求になるため、クラスの前方宣言を行う必要がある。

 class CSampleClass1;  // クラスの前方宣言
 
 class CSampleClass2
 {
 public:  // フレンド宣言を許可するため、publicメンバにする必要がある
    void func(CSampleClass1& x);
 };
 
 class CSampleClass1
 {
    friend void func(CSampleClass1& x);
    friend void Y::func(CSampleClass1& x);
 
    // ...略
 };


以下の例では、関数テンプレートをフレンド指定している。

 template <typename T>
 void func(T a);
 
 class CSampleClass
 {
    template <typename T>
    friend void func(T);
 };


template <typename T>の箇所が、friend指定子よりも前方にあることに注意する。
テンプレート仮引数の名前については、引数や戻り値で使用しない場合は省略できる。

フレンド関数の存在意義として最も大きい理由として、演算子のオーバーロードを幅広く実現できることである。
演算子の種類によっては、クラスの外部に記述しなければならないケースがある。
しかし、クラスの外部に記述する場合、privateメンバにアクセスできないため実装が難しくなるが、フレンド関数を活用することにより簡潔に記述できる。

上記の用途以外でのフレンド関数の使用は原則として避けて、他の設計を検討した方がよい。