12 - friends
friend
s allow to ignore access specifiers.
Friend declarations are not themselves affected by access specifiers so it doesn't matter where they are put.
Friend functions
A class can friend
a non-member function. This function will have access to private
and protected
members.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <iostream> class foo { private: int x = 1; // this is not a member function but a friend function declaration friend void func(foo f); }; void func(foo f) // friends can access private and protected members { std::cout << "f.x = " << f.x << "\n"; } int main() { foo f; func(f); } |
friend
function definition can also be placed directly within the class. In such case it's implicitly inline
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> class foo { private: int x = 1; friend void func(foo f) // implicitly inline { std::cout << "f.x = " << f.x << "\n"; } }; int main() { foo f; func(f); } |
Friend classes
A class can friend
another class. Friended class (and it's functions) will have access to private
and protected
members. This works only in 1 direction.
There are 3 syntax possibilities here:
friend simple-type-specifier;
friend typename-specifier;
friend elaborated-class-specifier;
The reasoning for limited syntax is simple: friend
declarations care only about the type name alone, code like friend const foo&;
makes no additional sense by introduction of qualifiers. Thus, only a subset of syntax have been allowed instead of something like decl-specifier-seq
.
Enough technicalities, now what's the real impact of it? As far as average C++ programmer would be concerned, there are 2 different usages:
-
friend identifier;
(from first 2)identifier
must already exist.if
identifier
does not name aclass
,struct
orunion
, it's not an error but the statement is ignored instead
-
friend class identifier;
(fromelaborated-class-specifier
) (theclass
can also bestruct
andunion
)if there is no type named
identifier
, it additionally acts as a forward declaration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using int_t = int; class quux {}; class bar {}; class foo { private: // friend declarations are not affected by access specifiers so it doesn't matter which keyword is here friend int_t; // ok: the type exists but is not a class - statement ignored friend fp_t; // error: type fp_t does not exist friend quux; // ok: quux can access private and protected members of foo friend class bar; // ok: bar can access private and protected members of foo friend class baz; // as above + also forward declares class baz friend class bzz {}; // error: friended class definition inside enclosing class not allowed }; |
I don't know the exact reason, but I guess it's similar why .
is allowed for static
members: it allows some templates to work out-of-the-box. Otherwise they would have the burdern of checking if the type is (not) a class and then providing different definitions to support both cases.
Friend members
A class can also friend
specific members of other classes, provided they themselves are accessible.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class foo { public: struct quux { /* ... */ }; int pub() const; private: int priv() const; }; class bar { // foo's default constructor (implicitly defined) can access all bar's members friend foo::foo(); friend foo::quux; // ok: all code of foo::quux can access all bar's members friend int foo::pub() const; // ok: foo::pub can access all bar's members friend int foo::priv() const; // error: foo::priv is private (not accessible in this context) }; |
Other rules
Friendship is a one-way relation: if
foo
friendsbar
,bar
can access all members offoo
but not vice versa.Friendship is not transitive: a friend of your friend is not your friend.
-
Friendship is not inherited (child classes (derived types) explained in inheritance chapter):
your friend's children are not your friends
your's children do not friend your friends
In short, friendship is a one-way relation that does not propagate.
Recommendation
Friends defeat the purpose of encapsulation and can be used to break class invariants. Be very cautious with their usage.
If two classes friend each other, they probably should be rewritten as one class or their public/non-public code is badly designed.
If one class friends a lot of other code, it probably has insufficient public interface.
Do not friend anything from the standard library! Each implementation can be different and contain multiple layers of internal code - since friendship is not transitive and does not propagate there are no guarantees whether the implementation of particular standard library entity will be able to use non-public members.