Friend Non-Member Function in C++

Friendship is magic

Inside the body of a class, a friend declaration (using the friend keyword) can be used to tell the compiler that some other class or function is now a friend. In C++, a friend is a class or function (member or non-member) that has been granted full access to the private or protected members of another class. In this way, a class can selectively give other classes or functions full access to their members without impacting anything else.

Friendship is always granted by the class whose members will be accessed (not by the class or function desiring access). Between access controls and granting friendship, a class always retains the ability to control who can access its members.

Friend non-member functions

A friend function is a function (member or non-member) that can access the private and protected members of a class as though it were a member of that class. In all other regards, a friend function is a normal function.

Let's take a look at an example:

#include <iostream>

class Accumulator
{
private:
    int m_value { 0 };

public:
    void add(int value) { m_value += value; }

    // Here is the friend declaration that makes non-member function void print(const Accumulator& accumulator) a friend of Accumulator
    friend void print(const Accumulator& accumulator);
};

void print(const Accumulator& accumulator)
{
    // Because print() is a friend of Accumulator
    // it can access the private members of Accumulator
    std::cout << accumulator.m_value;
}

int main()
{
    Accumulator acc{};
    acc.add(5); // add 5 to the accumulator

    print(acc); // call the print() non-member function

    return 0;
}

In this example, we have declared a non-member function named print() that takes an object of class Accumulator. Because print() is not a member of the Accumulator class, it would normally not be able to access private member m_value. However, the Accumulator class has a friend declaration making print(const Accumulator& accumulator) a friend.

Note that because print() is a non-member function (does not have an implicit object), we must explicitly pass an Accumulator object to print() to work with.

Defining a friend non-member inside a class

Much like member functions can be defined inside a class if desired, friend non-member functions can also be defined inside a class. The following examples defines friend non-member function print() inside the Accumulator class:

#include <iostream>

class Accumulator
{
private:
    int m_value { 0 };

public:
    void add(int value) { m_value += value; }

    // Friend functions defined inside a class are non-member functions
    friend void print(const Accumulator& accumulator)
    {
        // Because print() is a friend of Accumulator
        // it can access the private members of Accumulator
        std::cout << accumulator.m_value;
    }
};

int main()
{
    Accumulator acc{};
    acc.add(5); // add 5 to the accumulator

    print(acc); // call the print() non-member function

    return 0;
}

Although you might assume that because print() is defined inside Accumulator, that makes print() a member of Accumulator, this is not the case. Because print() is defined as a friend, it is instead treated as a non-member function (as if it had been defined outside Accumulator).

Syntactically preferring a friend non-member function

We prefer to use a non-member function over a member function.

#include <iostream>

class Value
{
private:
    int m_value{};

public:
    explicit Value(int v): m_value { v }  { }

    bool isEqualToMember(const Value& v) const;
    friend bool isEqualToNonmember(const Value& v1, const Value& v2);
};

bool Value::isEqualToMember(const Value& v) const
{
    return m_value == v.m_value;
}

bool isEqualToNonmember(const Value& v1, const Value& v2)
{
    return v1.m_value == v2.m_value;
}

int main()
{
    Value v1 { 5 };
    Value v2 { 6 };

    std::cout << v1.isEqualToMember(v2) << '\n';
    std::cout << isEqualToNonmember(v1, v2) << '\n';

    return 0;
}

In this example, we have defined two similar functions that check whether two Value objects are equal. isEqualToMember() is a member function, and isEqualToNonmember() is a non-member function.

In isEqualToMember(), we are passing one object implicitly and other explicitly. The implementation of the function reflects this, and we have to mentally reconcile that m_value belongs to the implicit object whereas v.m_value belongs to the explicit parameter.

In isEqualToNonmember(), both objects are passed explicitly. This leads to better parallelism in the implementation of the function, as the m_value member is always explicitly prefixed with an explicit parameter.