「friendって、カプセル化を壊すための抜け道みたいで怖い」「friend関数とfriendクラスって何が違うの?」「本当に使う場面ってあるの?」
こんな疑問はありませんか?
今回は、C++のfriend関数・friendクラスについて、実際にコンパイル・実行した結果を交えながら解説していきます。friend無しだと本当にコンパイルが通らないのかも、実際にエラーを出して確認します。
friendとは?
friendとは、
・クラスの外にある特定の関数やクラスだけに、そのクラスのprivate/protectedメンバへのアクセスを特別に許可する仕組み
です。「他人には見せない」がクラスの基本(カプセル化)ですが、friendを使うと「この関数・このクラスにだけは特別に見せる」という例外を作れます。
【実験】friendが無いとどうなる?
まずはfriendを使わずに、外部関数からprivateメンバへアクセスしようとしてみます。
class Vector2 {
private:
float x, y;
public:
Vector2(float x_, float y_) : x(x_), y(y_) {}
};
// friend宣言なしで、外部関数からprivateメンバに直接アクセスしようとする
void PrintVector(const Vector2& v) {
printf("(%f, %f)\n", v.x, v.y); // v.x, v.y はprivateなのでアクセスできないはず
}実際にコンパイルすると、次のエラーになりました。
friend_fail.cpp(12): error C2248: 'Vector2::x': private メンバー (クラス 'Vector2' で宣言されている) にアクセスできません。
friend_fail.cpp(5): note: 'Vector2::x' の宣言を確認してください
friend_fail.cpp(3): note: 'Vector2' の宣言を確認してください
friend_fail.cpp(12): error C2248: 'Vector2::y': private メンバー (クラス 'Vector2' で宣言されている) にアクセスできません。
friend_fail.cpp(5): note: 'Vector2::y' の宣言を確認してください
friend_fail.cpp(3): note: 'Vector2' の宣言を確認してください想定通り、外部関数からはprivateメンバに触れません。ここにfriend宣言を1行足すだけで、この関数だけ特別にアクセスできるようになります。
friend関数を実装してみる
class Vector2 {
private:
float x, y;
public:
Vector2(float x_, float y_) : x(x_), y(y_) {}
// friend宣言: PrintVectorだけは特別にprivateへアクセスできる
friend void PrintVector(const Vector2& v);
};
void PrintVector(const Vector2& v) {
printf("(%.1f, %.1f)\n", v.x, v.y); // friendなのでアクセスOK
}クラス定義の中にfriend void PrintVector(const Vector2& v);と書くだけです。この宣言の位置(public/private)は関係なく、どこに書いても効果は同じです。
friendクラスを実装してみる
特定の関数だけでなく、クラス全体をfriendにすることもできます。デバッグ専用クラスから、本来隠したいゲームクラスの内部状態を覗きたいときなどに便利です。
class Enemy; // 前方宣言
class EnemyDebugger {
public:
void DumpHp(const Enemy& e); // 定義はEnemyの後ろで行う
};
class Enemy {
private:
int hp = 100;
friend class EnemyDebugger; // EnemyDebuggerクラス全体をfriendにする
public:
void TakeDamage(int dmg) { hp -= dmg; }
};
void EnemyDebugger::DumpHp(const Enemy& e) {
printf("Enemy.hp = %d (friendクラス経由でprivateにアクセス)\n", e.hp);
}friend class EnemyDebugger;と書くだけで、EnemyDebuggerのすべてのメンバ関数からEnemyのprivateメンバにアクセスできるようになります。個別の関数ごとにfriend宣言を書く手間が省ける代わりに、アクセスを許可する範囲は広くなります。
実際に両方を実行した結果です。
[1] friend関数
PrintVector(v) = (1.0, 2.0)
[2] friendクラス
Enemy.hp = 70 (friendクラス経由でprivateにアクセス)ダメージ30を受けてhpが100→70になった様子が、EnemyDebuggerから直接読み取れています。
friendのよくある失敗例と対処法
①friendを付けずにアクセスしようとしてエラーになる
上で実演した通りです。error C2248系のエラーが出たら、まずアクセス指定子(public/private/protected)を疑い、それでも外部からどうしても触る必要があるなら friend を検討します。
②何でもfriendにして設計が崩れる
friendは便利ですが、乱用するとカプセル化の意味がなくなります。「本当にこの関数・このクラスだけに許可すべきか」を都度考え、可能ならgetter/setter(公開メンバ関数)で済ませられないか先に検討しましょう。
③friend関係は継承されない・相互にもならない
friend class EnemyDebugger;と書いても、EnemyDebuggerを継承したクラスは自動的にfriendにはなりません。同様に、EnemyがEnemyDebuggerのfriendになったからといって、EnemyDebuggerがEnemyのfriendになるわけでもありません。片方向のみの許可です。
注意点
- friendはアクセスを許可する側のクラスに書く
- friend関数は1つの関数だけ、friendクラスはそのクラスの全メンバ関数が対象
- friend関係は継承されず、相互でもない
- 乱用するとカプセル化が崩れるので、本当に必要な範囲だけに絞る
まとめ
- friendは特定の関数・クラスにだけprivateアクセスを許可する仕組み
- friend無しでは本当にコンパイルエラーになることを実際に確認した
- friend関数は個別に、friendクラスはまとめて許可できる
- 継承もされず相互にもならないので、許可する範囲を意識して使う
friendは「カプセル化を破る危険な機能」と紹介されがちですが、実際にはあくまで「明示的に許可した相手だけに開ける」仕組みです。使いどころを絞れば、デバッグ用クラスや演算子オーバーロードなどで素直に役立ちます。
ここまで読んでくださりありがとうございました。


