Friend Classes and Functions
An introduction to the friend
keyword, which allows classes to give other objects and functions enhanced access to its members
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 anywhereprotected
functions and members are accessible only to the same class, or from a child classprivate
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.
Mutable Class Members
An introduction to the mutable
keyword, which gives us more flexibility when working with const
objects.