C++のfriendとは
C++のfriendは、クラスのprivateメンバーやprotectedメンバーに対して、特定の関数や別のクラスからアクセスできるようにする仕組みです。
通常、privateやprotectedに指定されたメンバーは、外部から自由にアクセスできません。
しかし、friendとして指定された関数やクラスには、例外的に非公開メンバーへのアクセス権を与えられます。
friendは、演算子オーバーロードや密接に連携するクラス同士の実装などで利用されます。
friendの基本的な考え方
たとえば、次のクラスではvalueがprivateに指定されています。
class Sample {
private:
int value = 100;
};
そのため、クラス外から次のように直接アクセスすることはできません。
Sample obj;
// エラー
// std::cout << obj.value;
一方、外部関数をfriendとして指定すると、その関数からvalueへアクセスできます。
#include <iostream>
class Sample {
private:
int value = 100;
public:
friend void showValue(const Sample& obj);
};
void showValue(const Sample& obj) {
std::cout << obj.value << '\n';
}
int main() {
Sample obj;
showValue(obj);
}
実行結果は次のとおりです。
100
showValue()はSampleクラスのメンバー関数ではありません。
しかし、friendとして指定されているため、Sampleのprivateメンバーへアクセスできます。
friend関数とは
friend関数とは、クラスのメンバー関数ではないものの、そのクラスから特別なアクセス権を与えられた関数です。
friend関数の基本構文
基本的な書き方は次のとおりです。
class クラス名 {
private:
friend 戻り値型 関数名(引数);
};
具体例を見てみましょう。
#include <iostream>
class User {
private:
int age;
public:
User(int a) : age(a) {}
friend void printAge(const User& user);
};
void printAge(const User& user) {
std::cout << user.age << '\n';
}
int main() {
User user(30);
printAge(user);
}
printAge()は通常の非メンバー関数ですが、Userクラスからfriendとして指定されています。
そのため、
user.age
のようにprivateメンバーへ直接アクセスできます。
friend関数はメンバー関数ではない
friendとして指定しても、その関数が対象クラスのメンバー関数になるわけではありません。
たとえば、
friend void printAge(const User& user);
と宣言しても、
// エラー
// user.printAge();
とは呼び出せません。
通常の関数として、
printAge(user);
と呼び出します。
friendは、あくまでアクセス権を与えるための指定です。
friendとprivate・protectedの関係
friendは、クラスのprivateメンバーとprotectedメンバーの両方へアクセスできます。
friendはprivateをpublicにする機能ではない
次のようにfriend関数を宣言した場合を考えます。
class Sample {
private:
int value = 10;
friend void show(const Sample&);
};
show()からはvalueへアクセスできます。
しかし、それ以外の関数までvalueへアクセスできるようになるわけではありません。
つまり、friendはprivateを解除したりpublicへ変更したりする機能ではありません。
特定の関数やクラスに対してだけ、例外的なアクセス権を与える仕組みです。
protectedメンバーにもアクセスできる
friendとして指定された関数やクラスは、protectedメンバーにもアクセスできます。
#include <iostream>
class Sample {
protected:
int value = 100;
friend void show(const Sample&);
};
void show(const Sample& obj) {
std::cout << obj.value;
}
このように、friendには対象クラスの非publicメンバーへのアクセス権が与えられます。
friendクラスとは
friendでは、関数だけでなく、別のクラス全体にアクセス権を与えることもできます。
これをfriendクラスと呼びます。
friendクラスの基本例
#include <iostream>
class A {
private:
int value = 100;
friend class B;
};
class B {
public:
void show(const A& a) {
std::cout << a.value << '\n';
}
};
int main() {
A a;
B b;
b.show(a);
}
Aクラスの中で、
friend class B;
と宣言しています。
これによって、Bのメンバー関数はAのprivateメンバーやprotectedメンバーへアクセスできます。
クラス全体ではなく特定のメンバー関数だけfriendにできる
必要であれば、別クラス全体ではなく、特定のメンバー関数だけにアクセス権を与えることもできます。
#include <iostream>
class A;
class B {
public:
void show(const A& a);
};
class A {
private:
int value = 100;
friend void B::show(const A& a);
};
void B::show(const A& a) {
std::cout << a.value << '\n';
}
int main() {
A a;
B b;
b.show(a);
}
この場合、Bクラス全体ではなく、B::show()だけがAのfriendです。
必要以上の権限を与えたくない場合に利用できます。
friend関係の特徴
C++のfriendには、通常のクラス関係とは異なる重要な特徴があります。
friend関係は相互ではない
たとえば、
class A {
friend class B;
};
とした場合、BはAの非公開メンバーへアクセスできます。
しかし、AがBの非公開メンバーへアクセスできるようになるわけではありません。
両方向にアクセスさせたい場合は、それぞれにfriend指定が必要です。
class B;
class A {
friend class B;
};
class B {
friend class A;
};
friend関係は継承されない
friend関係は派生クラスへ自動的に引き継がれません。
class A {
friend class B;
};
class B {
};
class C : public B {
};
この場合、BはAのfriendですが、Cが自動的にAのfriendになるわけではありません。
friend関係は推移しない
friend関係には推移性もありません。
たとえば、
AはBをfriendにしている
BはCをfriendにしている
という関係があっても、
AはCをfriendにしている
ことにはなりません。
つまり、
A → B
B → C
であっても、
A → C
にはなりません。
friend宣言はどこに書くのか
friend宣言は、public、protected、privateのどこに記述しても、friendとして与えられるアクセス権は同じです。
public内に書く場合
class Sample {
public:
friend void show(const Sample&);
};
private内に書く場合
class Sample {
private:
friend void show(const Sample&);
};
どちらもfriendとしての意味は同じです。
friend宣言そのものが通常のメンバーとしてpublicやprivateになるわけではありません。
ただし、コードの可読性を高めるために、friend宣言をまとめて配置することはあります。
friendとthisポインタの関係
friendとthisの関係を理解するときは、「friend関数が非メンバー関数なのか、別クラスのメンバー関数なのか」を区別する必要があります。
非メンバーfriend関数にはthisがない
次のようなfriend関数を考えます。
class Sample {
private:
int value = 10;
friend void show(const Sample& obj);
};
show()は非メンバー関数です。
そのため、Sampleオブジェクトを指すthisポインタはありません。
対象オブジェクトを引数として受け取ります。
void show(const Sample& obj) {
std::cout << obj.value;
}
非staticメンバー関数にはthisがある
通常の非staticメンバー関数では、thisを利用できます。
class Sample {
private:
int value = 10;
public:
void show() {
std::cout << this->value;
}
};
なお、staticメンバー関数にはthisポインタがありません。
また、別クラスの非staticメンバー関数をfriendに指定した場合、その関数には所属するクラスのthisがあります。
したがって、「friend関数には必ずthisがない」と考えるのではなく、「非メンバーfriend関数にはthisがない」と理解するのが正確です。
friendが演算子オーバーロードで使われる理由
friendがよく登場する代表的な場面が、演算子オーバーロードです。
特に、operator<<によるストリーム出力でよく利用されます。
operator<<でfriendを使う例
#include <iostream>
class Point {
private:
int x;
int y;
public:
Point(int x, int y)
: x(x), y(y) {}
friend std::ostream& operator<<(std::ostream& os, const Point& point);
};
std::ostream& operator<<(std::ostream& os, const Point& point) {
os << "(" << point.x << ", " << point.y << ")";
return os;
}
int main() {
Point point(10, 20);
std::cout << point << '\n';
}
実行結果は次のとおりです。
(10, 20)
operator<<はPointの非メンバー関数ですが、friendとして指定されているため、
point.x
point.y
へ直接アクセスできます。
operator<<でfriendが必須なわけではない
operator<<を実装するからといって、必ずfriendを使う必要があるわけではありません。
たとえば、publicなgetterがあれば、friendを使わずに実装できます。
class Point {
private:
int x;
int y;
public:
int getX() const {
return x;
}
int getY() const {
return y;
}
};
std::ostream& operator<<(std::ostream& os, const Point& point) {
return os << "("
<< point.getX()
<< ", "
<< point.getY()
<< ")";
}
そのため、friendは「非メンバー関数からprivateやprotectedへ直接アクセスしたい場合の選択肢」と考えるのが適切です。
operator+でfriendを使う例
operator+でもfriendを利用できます。
friendを使ったoperator+の実装
#include <iostream>
class Vector2 {
private:
int x;
int y;
public:
Vector2(int x, int y)
: x(x), y(y) {}
friend Vector2 operator+(const Vector2& a, const Vector2& b);
void print() const {
std::cout << x << ", " << y << '\n';
}
};
Vector2 operator+(const Vector2& a, const Vector2& b) {
return Vector2(a.x + b.x, a.y + b.y);
}
int main() {
Vector2 a(10, 20);
Vector2 b(30, 40);
Vector2 c = a + b;
c.print();
}
実行結果は次のとおりです。
40, 60
operator+()はfriendなので、Vector2のprivateメンバーへ直接アクセスできます。
operator+でもfriendは必須ではない
operator+についても、必ずfriendで実装する必要はありません。
メンバー関数として実装したり、publicインターフェースだけを利用した非メンバー関数として実装したりする方法もあります。
どの方法が適切かは、クラス設計や演算子の対称性、暗黙変換の扱いなどによって変わります。
friendとgetterの違い
privateメンバーへ外部からアクセスしたい場合、friend以外にgetterを利用する方法もあります。
getterを使う例
class User {
private:
int age;
public:
User(int age)
: age(age) {}
int getAge() const {
return age;
}
};
外部関数では、次のように利用できます。
void printAge(const User& user) {
std::cout << user.getAge();
}
この場合、friendは必要ありません。
friendはアクセスできる相手を限定できる
getterをpublicとして公開すると、そのgetterを呼び出せるすべてのコードが値を取得できます。
一方、friendでは特定の関数やクラスだけに非公開メンバーへのアクセスを許可できます。
そのため、単純に「getterのほうが常に優れている」「friendはカプセル化を壊す」と考えるのは適切ではありません。
設計目的に応じて使い分けることが重要です。
friendのメリット
friendには、クラス設計上いくつかのメリットがあります。
非公開メンバーへ直接アクセスできる
特定の外部関数やクラスに限定して、privateやprotectedへアクセスさせることができます。
不要なgetterやsetterを公開せずに済む場合があります。
演算子オーバーロードを実装しやすい
operator<<など、非メンバー関数として実装するのが自然な演算子で利用しやすい仕組みです。
friend std::ostream& operator<<(std::ostream&, const Point&);
のようなコードはよく利用されます。
密接に関係するクラスを連携させやすい
コンテナとイテレータ、管理クラスと内部補助クラスなど、実装上強く連携するクラス同士で利用されることがあります。
friendのデメリットと注意点
friendは便利ですが、多用するとクラス設計が複雑になる可能性があります。
クラス間の依存が強くなる可能性がある
friend側のコードが対象クラスの内部構造へ直接依存すると、クラス内部の変更がfriend側にも影響しやすくなります。
たとえば、
obj.value
obj.count
obj.data
のように多数の内部メンバーへ直接依存していると、内部実装を変更しにくくなる場合があります。
多用すると保守性が低下する場合がある
大量のクラスや関数をfriendにすると、どのコードが内部状態へアクセスできるのか把握しにくくなる可能性があります。
そのため、friendは必要な範囲に限定することが重要です。
friendが必ずカプセル化を壊すわけではない
friendは対象クラス自身がアクセス先を明示的に指定する仕組みです。
そのため、多数の内部情報をpublic APIとして公開するより、特定の補助クラスや関数だけをfriendにするほうが、外部公開範囲を小さくできる場合もあります。
問題になるのはfriendそのものではなく、必要以上に多用することです。
friendを使う代表的な場面
friendが利用される代表的な場面には、次のようなものがあります。
| 使用場面 | 主な目的 |
|---|---|
operator<< | 非メンバー関数から内部情報を出力する |
operator+など | 非メンバー演算子から内部情報を扱う |
| イテレータ | コンテナ内部の情報へアクセスする |
| 補助クラス | 実装上密接に関連するクラスを連携させる |
| 内部補助関数 | public APIを増やさず内部情報を利用する |
ただし、いずれの場合もfriendが必須とは限りません。
publicインターフェースやメンバー関数で解決できる場合もあります。
friend関数とメンバー関数の違い
非メンバーfriend関数と非staticメンバー関数の違いを整理すると、次のようになります。
| 項目 | 非メンバーfriend関数 | 非staticメンバー関数 |
|---|---|---|
| 対象クラスのメンバーか | いいえ | はい |
| privateへのアクセス | friend指定されたクラスで可能 | 自クラスで可能 |
| protectedへのアクセス | friend指定されたクラスで可能 | 自クラスで可能 |
thisポインタ | ない | ある |
| 呼び出し例 | func(obj) | obj.func() |
| friend宣言 | 必要 | 不要 |
特に重要なのは、friend指定されても非メンバー関数がクラスのメンバーへ変わるわけではないという点です。
friendと継承の違い
friendと継承は、どちらもクラス同士に関係を持たせる機能ですが、目的は大きく異なります。
継承は型や機能の関係を表す
たとえば、
class Dog : public Animal {
};
のような継承では、派生クラスと基底クラスの関係を構築します。
friendはアクセス権を与えるだけ
一方、
friend class B;
は、特定のクラスに非公開メンバーへのアクセス権を与える仕組みです。
friendを指定しただけでは、継承関係や「is-a」の関係が生まれるわけではありません。
friendを使うときのポイント
friendを利用するときは、必要性とアクセス範囲を意識することが重要です。
必要最小限の相手だけfriendにする
クラス全体にアクセス権を与える必要がなければ、特定の関数だけをfriendにする方法も検討できます。
friend void B::show(const A&);
アクセス範囲を小さくすると、クラス間の依存関係を抑えやすくなります。
publicインターフェースで解決できないか確認する
friendを使う前に、
getter
setter
通常のメンバー関数
publicな操作メソッド
などで自然に解決できないか検討することも大切です。
ただし、public APIを増やすよりfriendで特定の相手だけに権限を与えるほうが自然なケースもあります。
friendを使った簡単な実例
最後に、friendの仕組みを理解するためのシンプルな例を紹介します。
#include <iostream>
class BankAccount {
private:
int balance;
public:
explicit BankAccount(int balance)
: balance(balance) {}
friend void printBalance(const BankAccount& account);
};
void printBalance(const BankAccount& account) {
std::cout << "残高: "
<< account.balance
<< "円\n";
}
int main() {
BankAccount account(10000);
printBalance(account);
}
実行結果は次のとおりです。
残高: 10000円
balanceはprivateなので通常は外部から直接アクセスできません。
しかし、
friend void printBalance(const BankAccount& account);
と宣言しているため、printBalance()からは直接アクセスできます。
なお、この例はfriendの仕組みを理解するための単純な例です。
実際のクラス設計では、publicインターフェースを利用したほうが適切な場合もあります。
C++のfriendについて覚えておきたいポイント
C++のfriendは、クラスのprivateやprotectedメンバーに対して、特定の関数やクラスからアクセスする権限を与える仕組みです。
代表的な書き方は次のとおりです。
friend void func();
friend class OtherClass;
重要なポイントをまとめると、次のようになります。
- friendとして指定しても、その関数が対象クラスのメンバーになるわけではありません。
- friendは
privateとprotectedの両方へアクセスできます。 - friend関係は相互ではありません。
- friend関係は継承されません。
- friend関係は推移しません。
- friend宣言は
public、protected、privateのどこに書いてもアクセス権の意味は同じです。 - 非メンバーfriend関数には
thisポインタがありません。 operator<<などの非メンバー演算子で利用されることがあります。- 演算子オーバーロードでfriendが必須というわけではありません。
- 多用するとクラス間の依存が強くなる可能性があります。
friendは「privateを無効にする機能」ではありません。
クラス自身が信頼する特定の関数やクラスに対して、必要な範囲だけ内部へのアクセス権を与える仕組みと理解するとよいでしょう。
以上、C++のfriendについてでした。
最後までお読みいただき、ありがとうございました。
