Friend Classes and Functions

An introduction to the friend keyword, which allows classes to give other objects and functions enhanced access to its members

Ryan McCombe
Updated

In our previous lessons covering classes and structs, we introduced access specifiers which allowed us to declare how accessible the variables and functions of our class are:

  • public functions and members are accessible from anywhere
  • protected functions and members are accessible only to the same class, or from a child class
  • private functions and members are accessible only within the same class

This allowed us to simplify our class interface by hiding away the inner workings of our class. We could use the public access specifier to make only some of our functions accessible to the outside world. Creating a smaller public interface in this way makes our classes friendlier to use and easier to maintain.

Encapsulation and Access Specifiers

A guide to encapsulation, class invariants, and controlling data access with public and private specifiers.

Protected Class Members

Learn about protected class members in C++, including how and when to use them in your code, especially in the context of inheritance and class design

However, this can be inflexible. There are many cases where we want more granular control.

For example, our class might have some data and methods that are of particular interest to another specific class or function. But we don't want to make them public for everyone - that's too heavy-handed and can eliminate the benefits of encapsulation.

Instead, we can solve these problems by having our class befriend the other class, using the friend keyword.

Friend Functions

A class can declare free functions (ie, a function that is not part of another class) as friends using the friend keyword, followed by the function signature. In the following example, MyClass befriends three different functions:

class MyClass {
  friend void SomeFunction();
  friend bool AnotherFunction(int);
  friend void OneMore(int, float);
};

More generally, the syntax is

friend return_type function_name(argument_list);

Any function our class identifies as a friend will have access to everything in our class, including protected and private members.

Here is a complete example, where our class befriends the void LogCalls(MyClass) function, giving it access to the private Calls variable:

#include <iostream>

// Forward Declarations
class MyClass;
void LogCalls(MyClass);

class MyClass {
  friend void LogCalls(MyClass);

public:
  void operator()(){ ++Calls; }

private:
  int Calls{0};
};

void LogCalls(MyClass Functor){
  std::cout << "That functor has been called "
    << Functor.Calls << " times";
}

int main(){
  MyClass Functor;
  Functor();
  Functor();
  Functor();
  LogCalls(Functor);
}
That functor has been called 3 times

Inline Friend Functions

To help with code organization, C++ also allows us to define friend functions within the class that they are a friend of. It looks like this:

#include <iostream>

class MyClass {
public:
  void operator()(){ ++Calls; }

  friend void LogCalls(MyClass Functor) {
    std::cout << "That functor has been called "
      << Functor.Calls << " times";  
  }

private:
  int Calls{0};
};

Whilst the previous example looks like LogCalls() is a member function, the use of the friend keyword in this context is creating a non-member function. This allows LogCalls() to be used like a free function, exactly as we did before:

#include <iostream>

class MyClass {/*...*/}; int main(){ MyClass Functor; Functor(); Functor(); Functor(); LogCalls(Functor); }
That functor has been called 3 times

Friend Classes

Our class can befriend another class, using the friend class syntax:

class MyClass {
  friend class Analytics;
};

This gives any member of Analytics access to any member of MyClass:

#include <iostream>

class MyClass {
  friend class Analytics;

public:
  void operator()(){ ++Calls; }

private:
  int Calls{0};
};

class Analytics {
public:
  static void LogCalls(MyClass Functor){
    std::cout << "That functor has been called "
      << Functor.Calls << " times";
  }
};

int main(){
  MyClass Functor;
  Functor();
  Functor();
  Functor();
  Analytics::LogCalls(Functor);
}
That functor has been called 3 times

Friend Member Functions

Where we want to befriend a class function, the syntax is similar to what we used to befriend a free function. We have to add the class name and the scope resolution operator to our friend statement:

// Forward Declaration
class MyClass;

class Analytics {
public:
  static void LogCalls(MyClass Functor);
};

class MyClass {
  friend void Analytics::LogCalls(MyClass);
};

The following code shows a complete example of this. Note, that we've moved the definition of Analytics above the definition of MyClass in this example. We've then also had to move the definition of Analytics::LogCalls below the definition of MyClass.

This is to ensure everything is appropriately defined before it is used. In real programs, this is rarely an issue, as our classes would be in separate header files:

#include <iostream>

class MyClass;

class Analytics {
public:
  static void LogCalls(MyClass Functor);
};

class MyClass {
  friend void Analytics::LogCalls(MyClass);

public:
  void operator()(){ ++Calls; }

private:
  int Calls{0};
};

void Analytics::LogCalls(MyClass Functor){
  std::cout << "That functor has been called "
    << Functor.Calls << " times";
}

int main(){
  MyClass Functor;
  Functor();
  Functor();
  Functor();
  Analytics::LogCalls(Functor);
}
That functor has been called 3 times

Friend Templates

We can befriend class and function templates by declaring template parameters in the normal way

class MyClass {
  // Befriending a class template
  template <typename>
  friend class Analytics;

  // Befriending a function template
  template <typename T>
  friend void LogCalls(T);
};

In this example, every instantiation of the template, including specializations of the template, is befriended.

If we want to befriend just a specific instantiation of a template class or function, we can provide the template parameters within our friend statement. This requires that the templates have already been declared:

// Forward Declarations
template <typename T>
class Analytics;

template <typename T>
void LogCalls(T);

class MyClass {
  friend class Analytics<MyClass>; 
  friend void LogCalls<MyClass>(MyClass); 
};

A complete, working example using both a class template and a function template is below:

#include <iostream>

class MyClass {
  template <typename>
  friend class Analytics;

  template <typename T>
  friend void LogCalls(T);

public:
  void operator()(){ ++Calls; }

private:
  int Calls{0};
};

template <typename T>
class Analytics {
public:
  static void LogCalls(T Functor){
    std::cout << "That functor has been called "
      << Functor.Calls << " times\n";
  }
};

template <typename T>
void LogCalls(T Functor){
  std::cout << "That functor has been called "
    << Functor.Calls << " times";
}

int main(){
  MyClass Functor;
  Functor();
  Functor();
  Functor();
  Analytics<MyClass>::LogCalls(Functor);
  LogCalls(Functor);
}
That functor has been called 3 times
That functor has been called 3 times

Private Constructors

Constructors can have access specifiers, just like any other member function. By making a constructor private, we prevent objects of that class from being created directly by external code.

This might seem strange - what good is a class if we can't create instances of it? The key is that the class itself, or its friends, can still call the private constructor. This gives us fine-grained control over how and when objects are created.

A common pattern is to provide a public static function that calls the private constructor and returns an object. This is known as a Factory Method, and it's useful for situations where object creation is complex or needs to be controlled.

For example, we can use a factory method to validate constructor arguments before creating an object:

#include <iostream>
#include <string>
#include <stdexcept>
#include <memory>

class User {
public:
  static std::unique_ptr<User> Create(
    const std::string& Name
  ) {
    if (Name.length() < 3) {
      throw std::invalid_argument(
        "Username is too short"
      );
    }
    // We can access the private constructor from
    // a static member function
    return std::unique_ptr<User>(new User(Name));
  }

  void Greet() const {
    std::cout << "Hello, " << Name << "!\n";
  }

private:
  User(const std::string& Name) : Name(Name) {}
  std::string Name;
};

int main() {
  // This would fail as the constructor is private
  // User u1("Direct"); 

  try {
    auto User1{User::Create("Alice")};
    User1->Greet();

    auto User2{User::Create("Bo")}; 
    User2->Greet();
  } catch(const std::exception& e) {
    std::cerr << "Error: " << e.what();
  }
}
Hello, Alice!
Error: Username is too short

This pattern can be extended using the friend keyword. Instead of a static member function, we can delegate the responsibility of object creation to a completely separate class.

This is useful when one class's lifecycle is managed by another, such as a GameObjectManager being responsible for creating all GameObject instances in a game.

By making the manager a friend of the object class, only the manager can create new objects, enforcing a clear and safe object management policy.

#include <iostream>
#include <string>
#include <vector>
#include <memory>

// Forward declaration
class Worker;

class Manager {
public:
  // This manager is responsible for creating workers
  std::shared_ptr<Worker> HireWorker(
    const std::string& Name);

  void ListTeam() const;

private:
  std::vector<std::shared_ptr<Worker>> Team{};
};

class Worker {
private:
  // Only the Manager class can call this constructor
  Worker(const std::string& Name) : Name{Name} {}
  std::string Name{};

  friend class Manager;
};

// Implementation must come after Worker is fully defined
std::shared_ptr<Worker> Manager::HireWorker(
  const std::string& Name
) {
  // We can call the private constructor because
  // Manager is a friend
  auto NewWorker{
    std::shared_ptr<Worker>{new Worker{Name}}
  };
  Team.push_back(NewWorker);
  return NewWorker;
}

void Manager::ListTeam() const {
  std::cout << "Current Team:\n";
  for(const auto& WorkerInstance : Team) {
    // Manager can also access private members
    std::cout << "- " << WorkerInstance->Name << '\n';
  }
}


int main() {
  Manager TheManager{};
  TheManager.HireWorker("Charlie");
  TheManager.HireWorker("Dana");

  TheManager.ListTeam();

  // The following line would cause an error
  // due to the constructor being private
  // Worker RogueWorker{"Rogue"}; 
}
Current Team:
- Charlie
- Dana

In this example, the Worker class has a private constructor, and it befriends Manager. This ensures that Worker objects can only be created via the Manager::HireWorker() method, giving the Manager class complete control over the creation and lifecycle of its Worker objects.

Friend Properties

It's worth noting some properties of the friend keyword:

1. A friend Declaration can Appear Anywhere in the Class

It doesn't matter where the friend statement appears within our class. It can come at the start, the end, or anywhere in between - the effect is the same.

2. A friend Declaration is not Subject to Access Specifiers

It doesn't matter if a friend statement appears in a public, protected, or private part of the class - the effect is the same

3. A friend Declaration is not Mutual

If A declares B a friend, that does not give A additional access to B.

For that, B needs to explicitly declare A a friend too.

4. A friend Declaration is not Transitive

If A declares B a friend, and B declares C a friend, that does not give C additional access to A.

For that, A needs to explicitly declare C a friend.

5. A friend Declaration is not Inherited

If Child inherits from Parent, and Parent declares SomeClass a friend, that does not grant SomeClass access to Child.

For that, Child needs to declare SomeClass a friend explicitly.

Summary

In this lesson, we explored the concept of friend functions and classes, which allow for controlled access to a class's private and protected members.

Main Points Learned

  • The friend keyword grants specific external functions and classes access to a class's private and protected members.
  • A class can declare a free function a friend.
  • Friend functions can be defined within the class that befriends them, and then called like a free function using argument-dependent lookup (ADL)
  • A class can declare another class as a friend, allowing all member functions of the friend class access to its private and protected members.
  • It's possible to declare specific member functions of another class as friends.
  • Friend declarations can also apply to template classes and functions, with the ability to specify all or particular instantiations.
  • Friend relationships are not mutual, transitive, or inherited, highlighting the need for explicit declarations to maintain secure and intended access levels.
  • The placement of a friend declaration within a class does not affect its access, and it is not affected by access specifiers.
Next Lesson
Lesson 102 of 128

Mutable Class Members

An introduction to the mutable keyword, which gives us more flexibility when working with const objects.

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Friend Functions and Encapsulation
How do friend functions and classes affect encapsulation?
Friend Functions vs. Public Members
When should I use friend functions instead of making members public?
Inheriting Friend Functions
Can friend functions be inherited by derived classes?
Self-Friend Class
Can a class befriend itself, and if so, what are the use cases?
Friend Function for Multiple Classes
Can a friend function access members of multiple classes?
Declaring Multiple Friends
What is the syntax for befriending multiple classes or functions at once?
Real-World Examples of Friend Functions
What are some real-world examples of using friend functions effectively?
Inline Friend Functions
Can a friend function be declared inline, and what are the implications?
Friend Functions and Virtual Inheritance
How do friend functions and classes work with virtual inheritance?
Overloaded Friend Functions
What happens if a friend function is overloaded?
Friend Functions in Namespaces
Can we have a friend function in a namespace?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant