Road to C++ Programmer #8 - Friend, Deleted, and Defaulted Functions

Last Edited: 9/24/2024

The blog post introduces the friend, deleted, and defaulted functions in C++.

C++ FDD

Friend Functions

When a function defined outside of a class needs to access the private members of that class, you can use friend functions.

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;
}

By using the friend keyword to declare that the double_value function is a friend function, the double_value function is granted access to the private attribute private.value and the private method private.add.

A primary use case for friend functions is when overloading an operator, particularly when the left side of the operator is not an instance of the class itself. Operator overloading within a class assumes that the class instance appears on the left-hand side, but a friend function does not have that assumption.

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 Functions

By using the static keyword, you can create a static member variable (or class-wide variable), which can be useful, for example, in generating a unique 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;
}

However, the assignment operator and copy constructor are available by default, which could allow the same unique_id to be assigned to different instances.

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;
}

To prevent this, you can declare the copy constructor and assignment operator as deleted functions, ensuring that these operations cannot be performed, thus preserving the uniqueness of 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 Functions

When you define a parameterized constructor, the default constructor of the class is automatically disabled.

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

To enable the default constructor, you can use the default keyword.

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

Resources