「C++の基礎 - フレンド」の版間の差分

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動
85行目: 85行目:
<br>
<br>
上記の用途以外でのフレンド関数の使用は原則として避けて、他の設計を検討した方がよい。<br>
上記の用途以外でのフレンド関数の使用は原則として避けて、他の設計を検討した方がよい。<br>
<br><br>
== フレンドクラス ==
<code>friend</code>指定子はクラスに対しても使用でき、指定されたクラスをフレンドクラスと呼ぶ。<br>
メンバ関数をフレンド関数にする場合と異なり、<code>private</code>メンバ関数もフレンドになる。<br>
<br>
フレンドクラスは、フレンド関数よりも許可を与える範囲が広いため、必要な場合に限り、フレンドクラスを使用するように検討する方がよい。<br>
<br>
フレンドクラスを指定する場合は、<code>friend class <クラス名>;</code>のように<code>class</code>キーワードを付加する。<br>
構造体の場合は、<code>friend struct <構造体名>;</code>を付加する。<br>
<br>
フレンドクラスを使用する場合、クラスや構造体であることが明示できるため、その定義が見えていなくても問題ない。<br>
<br>
また、フレンドクラスを指定する時、<code>class</code>や<code>struct</code>は省略できるため、<code>friend <クラス名または構造体名>;</code>と記述できる。<br>
この場合、クラスの定義が明示されていないため、エラーになる。<br>
<syntaxhighlight lang="c++">
class CSampleClass1
{
    friend class CSampleClass2;  // CSampleClass2の定義が見えていないが、classキーワードがあればOK
    friend CSampleClass2;        // CSampleClass2の定義が見えていないのでエラー (ただし、クラスの前方宣言があればOK)
};
class CSampleClass2
{
    // ...略
};
</syntaxhighlight>
<br>
テンプレートクラスもフレンドクラスとして指定することができる。<br>
<br>
また、テンプレート仮引数の名前は省略することができる。<br>
この場合、<code>class</code>キーワードを省略できないことに注意する。<br>
これは、テンプレートクラス自体はクラスではないため、テンプレートクラスの定義が<code>friend</code>指定子よりも前方に記述されているからといって、<br>
テンプレートクラスが見えていることにはならないからである。<br>
<syntaxhighlight lang="c++">
template <typename T>
class CSampleClass2
{
    // ...略
};
class CSampleClass1
{
    template <typename>
    friend class CSampleClass2;
};
</syntaxhighlight>
<br><br>
<br><br>


__FORCETOC__
__FORCETOC__
[[カテゴリ:C++]]
[[カテゴリ:C++]]

2022年12月5日 (月) 00:13時点における版

概要

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メンバにアクセスできないため実装が難しくなるが、フレンド関数を活用することにより簡潔に記述できる。

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


フレンドクラス

friend指定子はクラスに対しても使用でき、指定されたクラスをフレンドクラスと呼ぶ。
メンバ関数をフレンド関数にする場合と異なり、privateメンバ関数もフレンドになる。

フレンドクラスは、フレンド関数よりも許可を与える範囲が広いため、必要な場合に限り、フレンドクラスを使用するように検討する方がよい。

フレンドクラスを指定する場合は、friend class <クラス名>;のようにclassキーワードを付加する。
構造体の場合は、friend struct <構造体名>;を付加する。

フレンドクラスを使用する場合、クラスや構造体であることが明示できるため、その定義が見えていなくても問題ない。

また、フレンドクラスを指定する時、classstructは省略できるため、friend <クラス名または構造体名>;と記述できる。
この場合、クラスの定義が明示されていないため、エラーになる。

 class CSampleClass1
 {
    friend class CSampleClass2;  // CSampleClass2の定義が見えていないが、classキーワードがあればOK
    friend CSampleClass2;        // CSampleClass2の定義が見えていないのでエラー (ただし、クラスの前方宣言があればOK)
 };
 
 class CSampleClass2
 {
    // ...略
 };


テンプレートクラスもフレンドクラスとして指定することができる。

また、テンプレート仮引数の名前は省略することができる。
この場合、classキーワードを省略できないことに注意する。
これは、テンプレートクラス自体はクラスではないため、テンプレートクラスの定義がfriend指定子よりも前方に記述されているからといって、
テンプレートクラスが見えていることにはならないからである。

 template <typename T>
 class CSampleClass2
 {
    // ...略
 };
 
 class CSampleClass1
 {
    template <typename>
    friend class CSampleClass2;
 };