friend
keyword, which allows classes to give other objects and functions enhanced access to its membersIn 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 classThis 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.
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
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
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.
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
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
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
It’s worth noting some properties of the friend
keyword:
friend
declaration can appear anywhere in the classIt 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.
friend
declaration is not subject to access specifiersIt doesn’t matter if a friend
statement appears in a public
, protected
, or private
part of the class - the effect is the same
friend
declaration is not mutualIf 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.
friend
declaration is not transitiveIf 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.
friend
declaration is not inheritedIf 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.
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.
friend
keyword grants specific external functions and classes access to a class's private and protected members.friend
declaration within a class does not affect its access, and it is not affected by access specifiers.An introduction to the friend
keyword, which allows classes to give other objects and functions enhanced access to its members
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.