Friend Classes and Functions

An introduction to the friend keyword, which allows classes to give other objects and functions enhanced access to its members
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
3D Character Concept Art
Ryan McCombe
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.

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

Argument-Dependent Lookup (ADL)

The previous example relies on a fairly esoteric C++ mechanism called argument-dependent lookup, or ADL.

ADL changes where the compiler will search for function identifiers, based on the arguments we provide to those functions. Below, our call to SayHello() fails, as we haven’t told the compiler to look for it in the Greetings namespace:

#include <iostream>

namespace Greetings {
void SayHello() {
  std::cout << "Hello World"; }
}

int main() {
  SayHello();
}
error: 'SayHello': identifier not found

In the following example, we’ve updated our SayHello() function to accept an argument. In our main function, we provide such an argument. We still haven’t stated the namespace our function is in, but the compiler will now automatically check the namespace of our argument, leading it to find the function:

#include <iostream>

namespace Greetings {
struct SomeType {};
void SayHello(SomeType) {
  std::cout << "Hello World"; }
}

int main() {
  Greetings::SomeType Object;
  SayHello(Object);
}
Hello World

For similar reasons, the invocation to LogCall() in our earlier example only works because we’re helping the compiler find the function we defined within MyClass, by passing an instance of MyClass as an argument.

This mechanism is always in play for any realistic use of friend functions. The reason we declare a function as being a friend is so it can access restricted members of some class. This is only useful if we’re providing an instance of that class to the function and, in so doing, we’re also helping the compiler find that function using ADL.

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

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.

Was this lesson useful?

Next Lesson

Mutable Class Members

An introduction to the mutable keyword, which gives us more flexibility when working with const objects.
3D Character Concept Art
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Next Lesson

Mutable Class Members

An introduction to the mutable keyword, which gives us more flexibility when working with const objects.
3D Character Concept Art
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved