Friend Non-Member Functions

For much of this chapter and the last, we've been discussing the benefits of access controls, which provide a mechanism for controlling who can access the various members of a class. Private members can only be accessed by other members of the class, and public members can be accessed by everyone. In the Access functions lesson, we discussed the benefits of keeping your data private and creating a public interface for non-members to use.

However, there are situations where this arrangement is either not sufficient or not ideal.

For example, consider a database class that manages data storage. Now let's say you also want to export that data to various formats, but the code that handles exporting is complex with many options. You could put both the database management functions and the export management functions in the same class, but that would clutter things up and create a complex interface. You could also keep them separate: the database class manages storage, and some other export class manages all export capabilities. That creates a nice separation of responsibility. But the export class would then be unable to access the private members of the database class, and might not be able to do its job.

Alternatively, there are cases where syntactically we might prefer to use a non-member function over a member function (we'll show an example of this below). This is commonly the case when overloading operators, a topic we'll discuss in future lessons. But non-member functions have the same issue—they can't access the private members of the class.

If access functions (or other public member functions) already exist and are sufficient for whatever capability we're trying to implement, then great—we can (and should) just use those. But in some cases, those functions don't exist. What then?

One option would be to add new member functions to the class, to allow other classes or non-member functions to do whatever job they would otherwise be unable to do. But we might not want to allow public access to such things—perhaps those things are highly implementation dependent or prone to misuse.

What we really need is some way to subvert the access control system on a case-by-case basis.

Friendship is magic

The answer to our challenge is friendship.

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 and 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.

Key Concept
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.

For example, if our database class made the export class a friend, then the export class would be able to access all members of the database class directly. The export class could use this direct access to implement export of the database, while remaining structurally separate.

The friend declaration is not affected by access controls, so it does not matter where within the class body it is placed.

Now that we know what a friend is, let's look at specific examples where friendship is granted to non-member functions, member functions, and other classes. We'll discuss friend non-member functions in this lesson, and then look at friend classes and friend member functions in the next lesson on Friend classes and friend member functions.

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, the friend function is a normal function.

Let's take a look at an example of a simple class making a non-member function a friend:

#include <iostream>

class Inventory
{
private:
    int m_itemCount{ 0 };

public:
    void addItems(int count) { m_itemCount += count; }

    // Here is the friend declaration that makes non-member function void display(const Inventory& inventory) a friend of Inventory
    friend void display(const Inventory& inventory);
};

void display(const Inventory& inventory)
{
    // Because display() is a friend of Inventory
    // it can access the private members of Inventory
    std::cout << inventory.m_itemCount;
}

int main()
{
    Inventory inv{};
    inv.addItems(25); // add 25 items to the inventory

    display(inv); // call the display() non-member function

    return 0;
}

In this example, we've declared a non-member function named display() that takes an object of class Inventory. Because display() is not a member of the Inventory class, it would normally not be able to access private member m_itemCount. However, the Inventory class has a friend declaration making display(const Inventory& inventory) a friend, so this is now allowed.

Note that because display() is a non-member function (and thus does not have an implicit object), we must explicitly pass an Inventory object to display() 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 example defines friend non-member function display() inside the Inventory class:

#include <iostream>

class Inventory
{
private:
    int m_itemCount{ 0 };

public:
    void addItems(int count) { m_itemCount += count; }

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

int main()
{
    Inventory inv{};
    inv.addItems(25); // add 25 items to the inventory

    display(inv); // call the display() non-member function

    return 0;
}

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

Syntactically preferring a friend non-member function

In the introduction to this lesson, we mentioned that there were times we might prefer to use a non-member function over a member function. Let's show an example of that now.

#include <iostream>

class Score
{
private:
    int m_points{};

public:
    explicit Score(int p): m_points{ p }  { }

    bool isEqualToMember(const Score& s) const;
    friend bool isEqualToNonmember(const Score& s1, const Score& s2);
};

bool Score::isEqualToMember(const Score& s) const
{
    return m_points == s.m_points;
}

bool isEqualToNonmember(const Score& s1, const Score& s2)
{
    return s1.m_points == s2.m_points;
}

int main()
{
    Score score1{ 85 };
    Score score2{ 92 };

    std::cout << score1.isEqualToMember(score2) << '\n';
    std::cout << isEqualToNonmember(score1, score2) << '\n';

    return 0;
}

In this example, we've defined two similar functions that check whether two Score objects are equal. isEqualToMember() is a member function, and isEqualToNonmember() is a non-member function. Let's focus on how these functions are defined.

In isEqualToMember(), we're passing one object implicitly and the other explicitly. The implementation of the function reflects this, and we have to mentally reconcile that m_points belongs to the implicit object whereas s.m_points 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_points member is always explicitly prefixed with an explicit parameter.

You may still prefer the calling syntax score1.isEqualToMember(score2) over isEqualToNonmember(score1, score2). But when we cover operator overloading, we'll see this topic come up again.

Multiple friends

A function can be a friend of more than one class at the same time. For example, consider the following:

#include <iostream>

class Wind; // forward declaration of Wind

class Rain
{
private:
    int m_rainfall{ 0 };
public:
    explicit Rain(int rainfall) : m_rainfall{ rainfall } { }

    friend void printWeather(const Rain& rain, const Wind& wind); // forward declaration needed for this line
};

class Wind
{
private:
    int m_windspeed{ 0 };
public:
    explicit Wind(int windspeed) : m_windspeed{ windspeed } {  }

    friend void printWeather(const Rain& rain, const Wind& wind);
};

void printWeather(const Rain& rain, const Wind& wind)
{
    std::cout << "The rainfall is " << rain.m_rainfall <<
       " mm and the windspeed is " << wind.m_windspeed << " km/h\n";
}

int main()
{
    Wind wind{ 45 };
    Rain rain{ 12 };

    printWeather(rain, wind);

    return 0;
}

There are three things worth noting about this example. First, because printWeather() uses both Wind and Rain equally, it doesn't really make sense to have it be a member of either. A non-member function works better. Second, because printWeather() is a friend of both Wind and Rain, it can access the private data from objects of both classes. Finally, note the following line at the top of the example:

class Wind;

This is a forward declaration for class Wind. Class forward declarations serve the same role as function forward declarations—they tell the compiler about an identifier that will be defined later. However, unlike functions, classes have no return types or parameters, so class forward declarations are always simply class ClassName (unless they are class templates).

Without this line, the compiler would tell us it doesn't know what a Wind is when parsing the friend declaration inside Rain.

Doesn't friendship violate the principle of data hiding?

No. Friendship is granted by the class doing the data hiding with the expectation that the friend will access its private members. Think of a friend as an extension of the class itself, with all the same access rights. As such, access is expected, not a violation.

Used properly, friendship can make a program more maintainable by allowing functionality to be separated when it makes sense from a design perspective (as opposed to having to keep it together for access control reasons). Or when it makes more sense to use a non-member function instead of a member function.

However, because friends have direct access to the implementation of a class, changes to the implementation of the class will typically necessitate changes to the friends as well. If a class has many friends (or those friends have friends), this can lead to a ripple effect.

When implementing a friend function, prefer to use the public interface over direct access to members whenever possible. This will help insulate your friend function from future implementation changes and lead to less code needing to be modified and/or retested later.

Best Practice
A friend function should prefer to use the class interface over direct access whenever possible.

Prefer non-friend functions to friend functions

In the Benefits of data hiding (encapsulation) lesson, we mentioned that we should prefer non-member functions over member functions. For the same reasons given there, we should prefer non-friend functions over friend functions.

For example, in the following example, if the implementation of Inventory is changed (e.g., we rename m_itemCount), the implementation of display() will need to be changed as well:

#include <iostream>

class Inventory
{
private:
    int m_itemCount{ 0 }; // if we rename this

public:
    void addItems(int count) { m_itemCount += count; } // we need to modify this

    friend void display(const Inventory& inventory);
};

void display(const Inventory& inventory)
{
    std::cout << inventory.m_itemCount; // and we need to modify this
}

int main()
{
    Inventory inv{};
    inv.addItems(25); // add 25 items to the inventory

    display(inv); // call the display() non-member function

    return 0;
}

A better idea is as follows:

#include <iostream>

class Inventory
{
private:
    int m_itemCount{ 0 };

public:
    void addItems(int count) { m_itemCount += count; }
    int count() const { return m_itemCount; } // added this reasonable access function
};

void display(const Inventory& inventory) // no longer a friend of Inventory
{
    std::cout << inventory.count(); // use access function instead of direct access
}

int main()
{
    Inventory inv{};
    inv.addItems(25); // add 25 items to the inventory

    display(inv); // call the display() non-member function

    return 0;
}

In this example, display() uses access function count() to get the value of m_itemCount instead of accessing m_itemCount directly. Now if the implementation of Inventory is ever changed, display() will not need to be updated.

Best Practice
Prefer to implement a function as a non-friend when possible and reasonable.

Be cautious when adding new members to the public interface of an existing class, as every function (even trivial ones) adds some level of clutter and complexity. In the case of Inventory above, it's totally reasonable to have an access function to get the current item count. In more complex cases, it may be preferable to use friendship instead of adding many new access functions to the interface of a class.

Summary

Friend functions: Functions that have been granted full access to the private and protected members of a class. Friend functions can be either non-member functions or member functions of another class.

Granting friendship: Friendship is always granted by the class whose members will be accessed, using a friend declaration inside the class body. The friend declaration is not affected by access controls.

Friend non-member functions: Non-member functions that are declared as friends can access private members while remaining structurally separate from the class. They must be passed objects explicitly since they don't have an implicit object.

Defining friends inside classes: Friend non-member functions can be defined inside the class definition, but they remain non-member functions despite their location.

Multiple friends: A single function can be a friend of multiple classes, allowing it to access private members from objects of different class types.

Friendship and encapsulation: Friendship doesn't violate encapsulation because it is explicitly granted by the class. Friends should be considered extensions of the class with the same access rights.

Using the public interface: When implementing friend functions, prefer using the public interface over direct member access whenever possible. This reduces coupling and makes code more maintainable.

Preferring non-friends: When reasonable access functions exist or can be added without cluttering the interface, prefer using non-friend functions over friend functions for better encapsulation.

Friend non-member functions provide flexibility in class design, allowing external functions to work intimately with class internals when necessary while maintaining clear control over access. They are particularly useful for operator overloading and when separating concerns across different classes or functions.