Friend Classes and Friend Member Functions

Friend classes

A friend class is a class that can access the private and protected members of another class.

Here is an example:

#include <iostream>

class Database
{
private:
    int m_recordCount{};
    double m_dataSize{};
public:
    Database(int recordCount, double dataSize)
       : m_recordCount{ recordCount }, m_dataSize{ dataSize }
    { }

    // Make the Reporter class a friend of Database
    friend class Reporter;
};

class Reporter
{
private:
    bool m_showDataFirst{};

public:
    Reporter(bool showDataFirst)
         : m_showDataFirst{ showDataFirst }
    {
    }

    // Because Reporter is a friend of Database, Reporter members can access the private members of Database
    void generateReport(const Database& database)
    {
        if (m_showDataFirst)
            std::cout << database.m_dataSize << " GB, " << database.m_recordCount << " records\n";
        else // show records first
            std::cout << database.m_recordCount << " records, " << database.m_dataSize << " GB\n";
    }

    void setShowDataFirst(bool b)
    {
         m_showDataFirst = b;
    }
};

int main()
{
    Database database{ 10000, 2.5 };
    Reporter reporter{ false };

    reporter.generateReport(database);

    reporter.setShowDataFirst(true);
    reporter.generateReport(database);

    return 0;
}

Because the Reporter class is a friend of Database, Reporter members can access the private members of any Database object they have access to.

This program produces the following result:

10000 records, 2.5 GB
2.5 GB, 10000 records

A few additional notes on friend classes.

First, even though Reporter is a friend of Database, Reporter has no access to the *this pointer of Database objects (because *this is actually a function parameter).

Second, friendship is not reciprocal. Just because Reporter is a friend of Database does not mean Database is also a friend of Reporter. If you want two classes to be friends of each other, both must declare the other as a friend.

For Context
Sorry if this one hits a little close to home!

Class friendship is also not transitive. If class A is a friend of B, and B is a friend of C, that does not mean A is a friend of C.

Advanced note: Nor is friendship inherited. If class A makes B a friend, classes derived from B are not friends of A.

A friend class declaration acts as a forward declaration for the class being friended. This means we do not need to forward declare the class being friended before friending it. In the example above, friend class Reporter acts as both a forward declaration of Reporter and a friend declaration.

Friend member functions

Instead of making an entire class a friend, you can make a single member function a friend. This is done similarly to making a non-member function a friend, except the name of the member function is used instead.

However, in actuality, this can be a little trickier than expected. Let's convert the previous example to make Reporter::generateReport a friend member function. You might try something like this:

#include <iostream>

class Reporter; // forward declaration for class Reporter

class Database
{
private:
    int m_recordCount{};
    double m_dataSize{};
public:
    Database(int recordCount, double dataSize)
        : m_recordCount{ recordCount }, m_dataSize{ dataSize }
    {
    }

    // Make the Reporter::generateReport member function a friend of the Database class
    friend void Reporter::generateReport(const Database& database); // error: Database hasn't seen the full definition of class Reporter
};

class Reporter
{
private:
    bool m_showDataFirst{};

public:
    Reporter(bool showDataFirst)
        : m_showDataFirst{ showDataFirst }
    {
    }

    void generateReport(const Database& database)
    {
        if (m_showDataFirst)
            std::cout << database.m_dataSize << " GB, " << database.m_recordCount << " records\n";
        else // show records first
            std::cout << database.m_recordCount << " records, " << database.m_dataSize << " GB\n";
    }
};

int main()
{
    Database database{ 10000, 2.5 };
    Reporter reporter{ false };
    reporter.generateReport(database);

    return 0;
}

However, it turns out this won't work. In order to make a single member function a friend, the compiler has to have seen the full definition for the class of the friend member function (not just a forward declaration). Since class Database hasn't seen the full definition for class Reporter yet, the compiler will error at the point where we try to make the member function a friend.

Fortunately, this is easily resolved by moving the definition of class Reporter before the definition of class Database (either in the same file, or by moving the definition of Reporter to a header file and #including it before Database is defined).

#include <iostream>

class Reporter
{
private:
    bool m_showDataFirst{};

public:
    Reporter(bool showDataFirst)
        : m_showDataFirst{ showDataFirst }
    {
    }

    void generateReport(const Database& database) // compile error: compiler doesn't know what a Database is
    {
        if (m_showDataFirst)
            std::cout << database.m_dataSize << " GB, " << database.m_recordCount << " records\n";
        else // show records first
            std::cout << database.m_recordCount << " records, " << database.m_dataSize << " GB\n";
    }
};

class Database
{
private:
    int m_recordCount{};
    double m_dataSize{};
public:
    Database(int recordCount, double dataSize)
        : m_recordCount{ recordCount }, m_dataSize{ dataSize }
    {
    }

    // Make the Reporter::generateReport member function a friend of the Database class
    friend void Reporter::generateReport(const Database& database); // okay now
};

int main()
{
    Database database{ 10000, 2.5 };
    Reporter reporter{ false };
    reporter.generateReport(database);

    return 0;
}

However, we now have another problem. Because member function Reporter::generateReport() uses Database as a reference parameter, and we just moved the definition of Database below the definition of Reporter, the compiler will complain it doesn't know what a Database is. We can't fix this one by rearranging the definition order, because then we'll undo our previous fix.

Fortunately, this is also fixable in a couple of simple steps. First, we can add class Database as a forward declaration so the compiler will be okay with a reference to Database before it has seen the full definition of the class.

Second, we can move the definition of Reporter::generateReport() out of the class, after the full definition of Database class.

Here's what this looks like:

#include <iostream>

class Database; // forward declaration for class Database

class Reporter
{
private:
    bool m_showDataFirst{};

public:
    Reporter(bool showDataFirst)
        : m_showDataFirst{ showDataFirst }
    {
    }

    void generateReport(const Database& database); // forward declaration for Database needed for reference here
};

class Database // full definition of Database class
{
private:
    int m_recordCount{};
    double m_dataSize{};
public:
    Database(int recordCount, double dataSize)
        : m_recordCount{ recordCount }, m_dataSize{ dataSize }
    {
    }

    // Make the Reporter::generateReport member function a friend of the Database class
    // Requires seeing the full definition of class Reporter (as generateReport is a member)
    friend void Reporter::generateReport(const Database& database);
};

// Now we can define Reporter::generateReport
// Requires seeing the full definition of class Database (as we access Database members)
void Reporter::generateReport(const Database& database)
{
    if (m_showDataFirst)
        std::cout << database.m_dataSize << " GB, " << database.m_recordCount << " records\n";
    else // show records first
        std::cout << database.m_recordCount << " records, " << database.m_dataSize << " GB\n";
}

int main()
{
    Database database{ 10000, 2.5 };
    Reporter reporter{ false };
    reporter.generateReport(database);

    return 0;
}

Now everything will compile properly: the forward declaration of class Database is enough to satisfy the declaration of Reporter::generateReport() inside the Reporter class. The full definition of Reporter satisfies declaring Reporter::generateReport() as a friend of Database. And the full definition of class Database is enough to satisfy the definition of member function Reporter::generateReport().

If that's a bit confusing, see the comments in the program above. The key points are that a class forward declaration satisfies references to the class. However, accessing members of a class requires that the compiler have seen the full class definition.

If this seems like a pain—it is. Fortunately, this dance is only necessary because we're trying to do everything in a single file. A better solution is to put each class definition in a separate header file, with the member function definitions in corresponding .cpp files. That way, all of the class definitions would be available in the .cpp files, and no rearranging of classes or functions is necessary!

Summary

Friend classes: Classes that have been granted full access to the private and protected members of another class. All member functions of a friend class can access private members of the friended class.

Reciprocal friendship: Friendship is not reciprocal—if class A is a friend of class B, class B is not automatically a friend of class A. Both classes must explicitly declare each other as friends if bidirectional access is needed.

Transitive friendship: Friendship is not transitive—if class A is a friend of class B, and class B is a friend of class C, class A is not automatically a friend of class C.

Friend member functions: Individual member functions can be made friends instead of entire classes. This provides more granular control over access but requires careful ordering of class definitions.

Definition ordering challenges: Making a member function a friend requires the compiler to have seen the full definition of the class containing that member function. This often necessitates forward declarations and careful placement of class and function definitions.

Forward declarations: Class forward declarations allow references to classes before their full definition, but accessing class members requires the full definition. Friend class declarations act as forward declarations.

Practical organization: In real projects, placing class definitions in separate header files and member function definitions in corresponding .cpp files eliminates most definition ordering issues.

Friend classes and friend member functions provide fine-grained control over access to class internals. While friend classes offer simplicity, friend member functions provide more precise access control at the cost of additional complexity in managing definition dependencies.