C++プログラマへの道 #8 - フレンド, Deleted, Defaulted関数

Last Edited: 9/24/2024

このブログ記事では、C++におけるフレンド, Deleted, Defaulted関数を紹介します。

C++ FDD

フレンド関数

クラスの外で定義された関数が、そのクラスのプライベートメンバーにアクセスする必要がある場合、フレンド関数を使用できます。

class Private {
    friend void double_value(Private& private);
 
    private: 
        int value;
        void add(int x) {
            value += x;
        };
    
    public:
        void print() {
            cout << value << endl;
        };
    Private(int value) : value(value) {};
};
 
void double_value(Private& private) {
    private.add(private.value);
};
 
int main () {
 
    Private private(5);
    double_value(private);
    private.print(); // => 10!
    return 0;
}

friend キーワードを使用して double_value 関数がフレンド関数であることを示すと、 double_value 関数はプライベート属性 private.value とプライベートメソッド private.add にアクセスすることが許可されます。

フレンド関数の主な用途は、左側にクラス自身が来ない場合にオペレーターをオーバーロードする場合です。 クラス内でのオペレーターオーバーロードは、クラスインスタンスが左側に来ることを前提としますが、 フレンド関数にはその前提がありません。

class Counter {
    friend Counter operator+(int n, Conuter counter);
    private:
        int count;
 
    public:
    Counter(int count) : count(count) {};
 
    Counter operator+(int n) {
        int new_count = count + n;
        // The above assumes Counter is on the left. 
        return Counter(new_count);
    }
};
 
Counter operator+(int n, Counter counter) {
    int new_count  = n + counter.count;
    return Counter(new_count);
};
 
int main () {
    Counter c1(1);
    Counter c2 = 3 + c1; // c2.count = 4
    Counter c3 = c2 + 3; // c3.count = 7
    return 0;
}

Deleted関数

static キーワードを使用すると、クラス全体で使用できる静的メンバ変数(またはクラス変数)を作成できます。 これを利用して、たとえば一意のIDを生成することができます。

class User {
    static int next_id;
    public:
        int unique_id;
    User() {
        unique_id = next_id;
        next_id++;
    }
}
 
int User::next_id = 1;
 
int main () {
    User user1;
    User user2;
    User user3;
 
    cout << "User1 ID: " << user1.unique_id << endl;
    cout << "User2 ID: " << user2.unique_id << endl;
    cout << "User3 ID: " << user3.unique_id << endl;
    // => User1 ID: 1
    //    User2 ID: 2
    //    User3 ID: 3
 
    return 0;
}

ただし、デフォルトでコピーコンストラクタや代入演算子が利用可能なため、 同じ unique_id が異なるインスタンスに割り当てられる可能性があります。

int main () {
    User user1;
    User user2 = user1; // Copy Constructor
    User user3;
    
    user3 = user1; // Assignment Operator =
 
    cout << "User1 ID: " << user1.unique_id << endl;
    cout << "User2 ID: " << user2.unique_id << endl;
    cout << "User3 ID: " << user3.unique_id << endl;
    // => User1 ID: 1
    //    User2 ID: 1
    //    User3 ID: 1
 
    return 0;
}

これを防ぐために、コピーコンストラクタと代入演算子を Deleted関数 として宣言し、 これらの操作が実行できないようにして unique_id の一意性を確保します。

class User {
    static int next_id;
    public:
        int unique_id;
    User() {
        unique_id = next_id;
        next_id++;
    }
 
    User(User& user) = delete; // Copy Constructor Deleted
    User& operator=(User& user) = delete; // Assignment Operator Deleted
}

Defaulted関数

パラメータ付きコンストラクタを定義すると、クラスのデフォルトコンストラクタは自動的に無効化されます。

class Counter {
    public:
        int count;
    Counter (int count) : count(count) {};
};
 
int main () {
    Counter counter; // Err!
    return 0;
}

デフォルトコンストラクタを有効にするには、default キーワードを使用します。

class Counter {
    public:
        int count;
    Counter (int count) : count(count) {};
    Counter () = default;
};
 
int main () {
    Counter counter; // No Err
    return 0;
}

リソース