12 - friends

friends 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 a class, struct or union, it's not an error but the statement is ignored instead

  • friend class identifier; (from elaborated-class-specifier) (the class can also be struct and union)

    • 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 friends bar, bar can access all members of foo 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.