Ch13.10: friend: Letting One Outsider In

中文(简体)

1. The Operation That Belongs to the Type but Cannot Be a Member

Sometimes the operation you need takes two objects of the class and treats them as equals. The midpoint of two points, the distance between two points, the sum of two vectors: none of them is “a method of the left-hand object”, and none of them can be written outside the class either, because the data it needs is private.

friend is how the class gives one named outsider the access its own member functions already have. The permission is granted inside the class, by the class that owns the data:


class point
{
public:
	friend point midpoint(point const &a, point const &b) noexcept;   // granted here
private:
	::std::int_least32_t x_{};
	::std::int_least32_t y_{};
};

The friend line is a declaration of an ordinary free function that happens to be allowed to look inside. It is not a member: it has no this, it is not called with an object, and it is not part of the class’s list of member functions.

2. A Friend Function and a Friend Class


#include <fast_io.h>
#include <cstdint>
using namespace ::fast_io::io;

class point
{
public:
	point(::std::int_least32_t x, ::std::int_least32_t y) noexcept : x_{x}, y_{y}
	{
	}

	::std::int_least32_t sum() const noexcept
	{
		return x_ + y_;
	}

	// One named function may look inside.
	friend point midpoint(point const &a, point const &b) noexcept;

	// A whole class may look inside.
	friend class inspector;

private:
	::std::int_least32_t x_{};
	::std::int_least32_t y_{};
};

point midpoint(point const &a, point const &b) noexcept
{
	return point{(a.x_ + b.x_) / 2, (a.y_ + b.y_) / 2};
}

class inspector
{
public:
	static void dump(char const *label, point const &p) throws
	{
		println(::fast_io::mnp::os_c_str(label), ": x = ", p.x_, ", y = ", p.y_);
	}
};

int main()
{
	try
	{
		point a{0, 0};
		point b{10, 20};

		auto m{midpoint(a, b)};
		println("midpoint.sum() = ", m.sum());

		inspector::dump("midpoint", m);
	}
	catch throws (::std::error e)
	{
		print("failed: ", e, "\n");
	}
}

midpoint.sum() = 15
midpoint: x = 5, y = 10

Two grants, two shapes:

Why midpoint(a, b) is callable at all. A function declared only inside a class as a friend is invisible to ordinary name lookup, but it is found when one of its arguments is of the class type — that is argument-dependent lookup, and it is why midpoint(a, b) resolves without a second declaration at namespace scope. If you want to be able to call it with the class type nowhere in the arguments, declare it at namespace scope as well.

3. What friend Is Not

People expect What actually happens
“Friendship goes both ways.” It does not. If inspector is a friend of point, then inspector may look inside a point — but point gains nothing, and inspector’s own private members are still private to point.
“A friend of my friend is my friend.” It is not transitive. inspector being a friend of point says nothing about any other class.
“I can declare myself a friend.” You cannot. Only the class that owns the data can grant access to it. Friendship is given, never taken.
“A friend is a member.” It is not. There is no this inside a friend function, and it is not listed among the class’s member functions.

4. Reach for It Last

friend is the narrowest hole you can open in the wall, and it is still a hole. Before granting one, check the cheaper options in this order:

  1. Can a member function do it? If the operation has a natural subject object, make it a member. midpoint has no natural subject — that is why it is not one.
  2. Can the public interface do it? A friend function built on public accessors (a.x(), a.y()) needs no grant at all. Use that whenever the accessors already exist; it keeps the class closed and the function testable.
  3. Is it about the class, not an object? Then a static member function is the right shape (Ch13.9).
  4. Otherwise — symmetric operations between two objects are the classic case — friend is the tool.
friend creates coupling. A friend function is now part of the class’s implementation: change the data members and the friend breaks, even though it is not visibly in the class. That is acceptable for the two or three operations that genuinely belong to the type’s interface, and it is a maintenance trap if you hand out friendship to whatever needs a shortcut. If several outsiders need the same data, the design question is usually whether that data should have a public accessor — not whether to add a fifth friend.
This is also how operators are written. An operator such as + or == that takes two objects of the class has the same shape as midpoint, and is usually declared as a friend for the same reason. Operators are covered later in the tutorial, once you have classes and references in hand.