C++のfriendについて

AI実装検定のご案内

目次

C++のfriendとは

C++のfriendは、クラスのprivateメンバーやprotectedメンバーに対して、特定の関数や別のクラスからアクセスできるようにする仕組みです。

通常、privateprotectedに指定されたメンバーは、外部から自由にアクセスできません。

しかし、friendとして指定された関数やクラスには、例外的に非公開メンバーへのアクセス権を与えられます。

friendは、演算子オーバーロードや密接に連携するクラス同士の実装などで利用されます。

friendの基本的な考え方

たとえば、次のクラスではvalueprivateに指定されています。

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として指定されているため、Sampleprivateメンバーへアクセスできます。

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へアクセスできるようになるわけではありません。

つまり、friendprivateを解除したり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のメンバー関数はAprivateメンバーや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;
};

とした場合、BAの非公開メンバーへアクセスできます。

しかし、ABの非公開メンバーへアクセスできるようになるわけではありません。

両方向にアクセスさせたい場合は、それぞれに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 {
};

この場合、BAのfriendですが、Cが自動的にAのfriendになるわけではありません。

friend関係は推移しない

friend関係には推移性もありません。

たとえば、

AはBをfriendにしている
BはCをfriendにしている

という関係があっても、

AはCをfriendにしている

ことにはなりません。

つまり、

A → B
B → C

であっても、

A → C

にはなりません。

friend宣言はどこに書くのか

friend宣言は、publicprotectedprivateのどこに記述しても、friendとして与えられるアクセス権は同じです。

public内に書く場合

class Sample {
public:
    friend void show(const Sample&);
};

private内に書く場合

class Sample {
private:
    friend void show(const Sample&);
};

どちらもfriendとしての意味は同じです。

friend宣言そのものが通常のメンバーとしてpublicprivateになるわけではありません。

ただし、コードの可読性を高めるために、friend宣言をまとめて配置することはあります。

friendとthisポインタの関係

friendthisの関係を理解するときは、「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なので、Vector2privateメンバーへ直接アクセスできます。

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には、クラス設計上いくつかのメリットがあります。

非公開メンバーへ直接アクセスできる

特定の外部関数やクラスに限定して、privateprotectedへアクセスさせることができます。

不要な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円

balanceprivateなので通常は外部から直接アクセスできません。

しかし、

friend void printBalance(const BankAccount& account);

と宣言しているため、printBalance()からは直接アクセスできます。

なお、この例はfriendの仕組みを理解するための単純な例です。

実際のクラス設計では、publicインターフェースを利用したほうが適切な場合もあります。

C++のfriendについて覚えておきたいポイント

C++のfriendは、クラスのprivateprotectedメンバーに対して、特定の関数やクラスからアクセスする権限を与える仕組みです。

代表的な書き方は次のとおりです。

friend void func();
friend class OtherClass;

重要なポイントをまとめると、次のようになります。

  • friendとして指定しても、その関数が対象クラスのメンバーになるわけではありません。
  • friendはprivateprotectedの両方へアクセスできます。
  • friend関係は相互ではありません。
  • friend関係は継承されません。
  • friend関係は推移しません。
  • friend宣言はpublicprotectedprivateのどこに書いてもアクセス権の意味は同じです。
  • 非メンバーfriend関数にはthisポインタがありません。
  • operator<<などの非メンバー演算子で利用されることがあります。
  • 演算子オーバーロードでfriendが必須というわけではありません。
  • 多用するとクラス間の依存が強くなる可能性があります。

friendは「privateを無効にする機能」ではありません。

クラス自身が信頼する特定の関数やクラスに対して、必要な範囲だけ内部へのアクセス権を与える仕組みと理解するとよいでしょう。

以上、C++のfriendについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次