Programming glossary 61 terms mentioned in this lesson
Select any term for a clear definition. The return button brings you back to the exact term link you used.
Lesson: friend Functions — Surgical Access without Abandoning Encapsulation
To a beginner, C++’s friend can look anti–object-oriented: “a mechanism to let a global function access anything from a class.”
Used carelessly, it is dangerous. Used well, it’s a small, explicit hole in the wall that enables clean, symmetric APIs
(e.g., comparisons, streaming) without turning data members public or bloating class interfaces.
Definition: A friend function is a non-member that a class explicitly authorises to access its private and protected members.
The function remains a free function (not a method).
Programming Insight (AI) — Should this be a friend?
- Paste your class and target function. Ask AI to classify: member vs non-member non-friend vs non-member friend, with justification.
- Request a “minimum access” rewrite: smallest friend declaration surface, or alternative via public API.
1) Why a Free Function Instead of a Method?
- Symmetry: operations conceptually involving two operands (e.g.,
lhs == rhs,lhs + rhs,ostream << obj) don’t have a natural “owner”. - Conversions: a non-member operator allows implicit conversions on both operands; a member operator only on the right-hand side.
- Encapsulation: keep representation private; selectively grant access to specific helpers.
2) Basic Pattern — Granting a Function Access
class Box {
public:
explicit Box(int w, int h): w_{w}, h_{h} {}
int width() const noexcept { return w_; }
int height() const noexcept { return h_; }
// Friend declaration (not a member). Still declared in the surrounding namespace.
friend bool same_area(const Box& a, const Box& b);
private:
int w_, h_;
};
// Definition (namespace scope). Has access due to 'friend'.
bool same_area(const Box& a, const Box& b) {
return a.w_ * a.h_ == b.w_ * b.h_;
}
Notes: Declaring a friend inside the class does not make it a member. It only grants access.
3) Friends for Symmetric Operators
Equality & Ordering:
class Point {
public:
Point(int x, int y): x_{x}, y_{y} {}
friend bool operator==(const Point& a, const Point& b) { return a.x_ == b.x_ && a.y_ == b.y_; }
friend bool operator!=(const Point& a, const Point& b) { return !(a == b); }
// C++20: could use 'friend auto operator<=>(const Point&, const Point&) = default;'
private:
int x_, y_;
};
Streaming: idiomatic “hidden friend” so ADL finds it:
#include <iostream>
class Vec2 {
public:
Vec2(float x, float y): x_{x}, y_{y} {}
friend std::ostream& operator<<(std::ostream& os, const Vec2& v) {
return os << '(' << v.x_ << ',' << v.y_ << ')';
}
private:
float x_, y_;
};
Defining the friend inside the class makes it a hidden friend (not visible by unqualified lookup), but discoverable via ADL
when you write std::cout << v. This avoids polluting the surrounding namespace.
Programming Insight (AI) — Operator Strategy
- Ask AI which operators should be members vs non-member friends for your type (with rules about symmetry & conversions).
- Request a C++20 rewrite using
= defaultfor==/<=>, if suitable.
4) Friends Across Two (or More) Classes
Sometimes the abstraction you want is a free function operating on multiple classes, each with private state.
struct Rgba; // fwd
struct Gray {
explicit Gray(unsigned char v): v_{v} {}
friend bool equivalent(const Gray& g, const Rgba& c); // grant access
private: unsigned char v_{};
};
struct Rgba {
unsigned char r{}, g{}, b{}, a{255};
friend bool equivalent(const Gray& g, const Rgba& c); // grant access
};
bool equivalent(const Gray& g, const Rgba& c) {
unsigned char luminance = static_cast<unsigned char>(0.2126*c.r + 0.7152*c.g + 0.0722*c.b);
return luminance == g.v_; // direct access to both representations
}
Each class chooses to befriend exactly the function it needs. Friendship is not transitive and not inherited.
5) Friend vs Member vs Non-member Non-friend (Decision Guide)
- Member (method): operation conceptually belongs to the object (
obj.size()), or needs virtual dispatch. - Non-member, non-friend: can be implemented entirely via the public API; best for keeping encapsulation intact.
- Non-member, friend: operation is symmetric or cross-type and cannot be implemented without private access (e.g., efficient comparisons, formatting, tight algorithms).
Programming Insight (AI) — Minimise Friendship
- Have AI attempt a “no-friend” implementation first. If it fails or is inefficient, it will propose a narrow friend signature.
6) Friend Classes and Templates (use sparingly)
Friend class: grants wide access; only for tightly coupled helpers (e.g., PIMPL, builders).
class Engine {
friend class EngineTester; // test helper with privileged access
// ...
};
Templated friendships: befriend a function template (hidden friend pattern), or an entire class template parameterised on T.
template<class T> class Box {
T value_;
public:
explicit Box(T v): value_{v} {}
template<class U>
friend bool equal_type_agnostic(const Box& a, const Box<U>& b) {
return static_cast<long double>(a.value_) == static_cast<long double>(b.value_);
}
};
7) Const-Correctness, Inline & Performance
- Friends respect
const; they can’t mutate aconstobject unless the class exposesmutableor non-const members. - Defining a small friend inside the class makes it implicitly
inline; the compiler may optimise it like a method.
8) Common Pitfalls (and how to avoid them)
- Over-friending: don’t declare broad friend classes when a single friend function works.
- Leaking invariants: friends must honour the same invariants as methods; keep implementations small and obvious.
- Namespace clutter: prefer hidden friends for operators to avoid global pollution; rely on ADL.
- Testing shortcuts: making tests friends is acceptable, but consider public “observer” methods that don’t break encapsulation.
Programming Insight (AI) — Invariant Guard
- Ask AI to add asserts/preconditions in your friend implementations that mirror the class invariants.
9) Worked Example — Building a Clean, Symmetric API
#include <ostream>
#include <stdexcept>
class Rational {
public:
Rational(long n, long d) : num_{n}, den_{d} {
if (den_ == 0) throw std::invalid_argument("denominator 0");
normalise();
}
// Symmetric friends:
friend bool operator==(const Rational& a, const Rational& b) {
return a.num_ == b.num_ && a.den_ == b.den_;
}
friend bool operator!=(const Rational& a, const Rational& b) { return !(a == b); }
friend Rational operator+(const Rational& a, const Rational& b) {
return Rational(a.num_*b.den_ + b.num_*a.den_, a.den_*b.den_);
}
friend std::ostream& operator<<(std::ostream& os, const Rational& r) {
return os << r.num_ << '/' << r.den_;
}
private:
long num_, den_;
void normalise(); // reduce fraction; keeps invariant
};
Non-member, friend operators keep the type’s interface small, the representation private, and the API symmetric and natural.
10) Mini Exercises
- Hidden friend stream: For class
Span{ptr,size}, implement a hidden friendoperator<<that prints[p..p+size)without exposing members. - Two-type algorithm: Given
MatrixandVectorwith private layouts, implement a freedot(Vector, Vector)and a freerow(Matrix, i)as friends where necessary; justify any friendship. - Remove an unnecessary friend: Refactor a friend function to use public getters only; explain the trade-off (encapsulation vs performance).
Programming Insight (AI) — Check My Friendship
- Paste your solutions; AI will flag over-broad friendship, missing ADL, and suggest a safer non-friend alternative if possible.
Summary Checklist
- Friendship is explicit and narrow: grant access to specific functions, not broad classes, whenever possible.
- Keep representation private: use friends to enable symmetric/free operations without exposing internals widely.
- Prefer non-member, non-friend when the public API suffices; use
friendonly when necessary for correctness or efficiency. - Use hidden friends for operators (good ADL, minimal namespace clutter).
- Friendship isn’t inherited or transitive: every class must opt-in to every friend it needs.
- Respect invariants: friend code must maintain the same guarantees as methods.
Advanced perspective: privileged access needs a narrower reason than convenience
A friend declaration should expose one necessary relationship, not compensate for a confused interface
A friend function is a non-member that a class explicitly authorises to access its private and protected members. The declaration changes access; it does not turn the function into a member, supply a this pointer, make friendship reciprocal, pass the privilege to derived classes or extend it transitively to the friend's own friends.
That definition is necessary, but it does not decide when friendship is good design. The operative question is why this operation needs representation access while remaining outside the class. If the public interface already expresses the operation clearly and efficiently enough, an ordinary non-member preserves the boundary. If the operation belongs to one object's responsibility, a member may say so directly. Friendship is justified only when a non-member relationship and narrow private access are both part of the real design.
| Proposed reason | What it establishes | What remains unproved |
|---|---|---|
| "The compiler says the member is private." | The current implementation lacks access. | That the operation should know the representation. |
| "The operator has two operands." | A non-member may give the operands a symmetric interface. | That public observations are insufficient. |
| "A friend avoids writing getters." | The function can reach fields directly. | That those fields form the correct long-term dependency. |
| "This one function must preserve a cross-object invariant." | A specific privileged relationship may exist. | How narrow the granted signature can be and how it will be tested. |
A hidden friend remains a namespace function
#include <iostream>
class Coordinate {
public:
Coordinate(int x, int y) : x_{x}, y_{y} {}
friend bool same_position(const Coordinate& left, const Coordinate& right) {
return left.x_ == right.x_ && left.y_ == right.y_;
}
private:
int x_;
int y_;
};
int main() {
const Coordinate first{2, 5};
const Coordinate second{2, 5};
std::cout << std::boolalpha << same_position(first, second) << '\n';
}
same_position is defined inside the class definition, but it is not a member of Coordinate. Both operands are explicit parameters, there is no this, and the call uses ordinary function syntax. Its privilege is access to x_ and y_; its identity remains that of a function in the surrounding namespace.
Why does the unqualified call find it? A function first declared in this way is not made generally visible to ordinary unqualified lookup merely by the in-class friend definition. Argument-dependent lookup also searches functions associated with the argument types. Because both arguments are Coordinate objects, that lookup can find the hidden friend. The function compares 2 with 2 and 5 with 5, returns true, and std::boolalpha causes the word true to be printed.
| Property | What the example establishes | Common mistaken conclusion |
|---|---|---|
| Membership | same_position is a non-member namespace function. | Defining it inside the class makes it a method. |
| Access | The function may read the two private coordinate members. | All non-members in the namespace gain the same access. |
| Lookup | ADL finds the function for Coordinate arguments. | The name becomes generally visible everywhere after the class definition. |
| Constness | The two const references permit observation of the objects. | Friend status allows mutation through a const object. |
Select member, non-member or friend by responsibility
| Form | Natural model | Representation access | Pressure test |
|---|---|---|---|
| Public member | One object is the receiver and the operation belongs to its behaviour. | Direct through this. | Would object.operation() state the responsibility honestly? |
| Non-member, non-friend | All operands are explicit and the public contract is sufficient. | None beyond public access. | Can the implementation survive a representation change? |
| Friend non-member | The operation is symmetric or cross-type and needs a narrow representation fact. | Exactly what the granting class permits. | Which private fact is necessary, and which invariant must the function respect? |
An output operator often needs the stream as its left operand, which makes a non-member form natural. That does not automatically make it a friend. If public queries provide the complete printable state, the operator can be an ordinary client. A value comparison may likewise use public observations. Direct access can be useful where it avoids exposing representation solely for an operator, but the decision must name the dependency rather than repeat the word "idiomatic".
Non-member binary operators can also permit conversions on both operands where a member form would privilege the left-hand object. This supports symmetric arithmetic and comparison interfaces. It still does not mean every symmetric operator needs private access. Symmetry answers the member-versus-non-member question; representation needs answer the friend-versus-non-friend question.
Access is compile-time privilege, not exemption from the invariant
A friend can read and, when the object is non-const, modify private state within the rules of the language. It therefore joins the set of code that must preserve the class invariant. The compiler checks whether access is permitted; it does not check whether assigning a particular combination of private values makes domain sense.
This is why friendship should be reviewed with the class's constructors and mutating members. If a friend creates an invalid intermediate state that can escape, or retains an alias beyond the object's lifetime, the fact that the access was formally authorised offers no defence. Privilege identifies responsibility. It does not remove it.
| Review point | Question | Evidence required |
|---|---|---|
| Read access | Why can the public interface not express the needed observation? | A concrete representation fact or material interface cost. |
| Write access | Which valid state transition may the friend perform? | Precondition, postcondition and unchanged state on failure. |
| Lifetime | Does the friend return or retain a reference to private storage? | An owner and validity interval that cover every use. |
| Change impact | Which functions must change when the representation changes? | The friend set is small enough to inspect and update. |
A friend function is narrower than a friend class
friend class Inspector; grants every member function of Inspector access to the granting class's private and protected members. That includes methods added later. If only one inspection operation requires access, befriending the entire class authorises more code than the current requirement names.
A single friend function can express the narrower relationship. Sometimes two types are intentionally coupled so tightly that class friendship is accurate, such as an implementation helper whose whole responsibility depends on the representation. Even then, record the shared invariant and ownership model. "They are in the same subsystem" is an organisational fact, not an access policy.
Friendship is neither inherited nor transitive. If Inspector is a friend of Engine, a class derived from Inspector does not acquire that friendship merely by inheritance, and an Inspector friend does not become an Engine friend. It is also not reciprocal: Inspector may access Engine only because Engine granted that direction.
Hidden friends limit lookup, not access responsibility
Placing an operator as a hidden friend can keep it associated with the operand type and make ADL the normal discovery route. Defining it in the class also makes the definition inline in the relevant language sense, allowing identical definitions across translation units. Neither property guarantees that the compiler will inline each call, and neither makes private coupling cheaper to maintain.
Hidden friendship is particularly useful for operators that should participate when their type is an argument but should not become an unrelated candidate during ordinary namespace lookup. That is an overload-set decision. The function still needs a stable contract, correct constness and only the access its implementation genuinely requires.
Correction to the earlier cross-specialisation Box example: the friend defined for Box<T> can access the private state of that granting specialisation. It does not thereby gain private access to an arbitrary Box<U> used as the other operand; different class-template specialisations do not automatically share friendship. The attempted conversion of both values to long double imposes another unjustified assumption: a valid box element type need not support or preserve meaning through that conversion. The example therefore fails both its access claim and its alleged generality, and must remain a warning rather than code to imitate.
Testing should normally observe the contract
Making a test fixture a friend can expose internal state quickly, but the test then depends on representation rather than solely on behaviour. Such tests may be justified for difficult low-level invariants or diagnostics. They should not replace contract tests that construct objects, perform public operations and observe promised results. A test suite that fails whenever two private members are reorganised can obstruct the very representation freedom encapsulation was meant to provide.
Before befriending a test, ask whether a safe public observation is already part of the type's real contract. Do not add a production setter solely for tests; that damages the interface. Equally, do not grant a whole testing class private access when one focused diagnostic function or internal assertion would meet the genuine need.
Programming Insight (AI): make the model attempt the no-friend design first
Ask an AI system to implement the operation through the public interface and name any cost or missing fact. If it then proposes friendship, require the exact signature, the private representation fact being used, the invariant that must remain true and the lifetime of any returned alias. Reject friendship added merely to make an access error disappear.
For a hidden friend, also require a lookup explanation. Which argument type makes ADL consider the function? Would a qualified or argument-free call still find it? For a friend class or template, list every specialisation or future member that actually receives access. The diagram should show the privilege that C++ grants, not the broader privilege the generated explanation happens to imagine.
Transfer task: justify each privilege separately
Design an Interval type whose invariant is lower <= upper. The public interface already provides lower() and upper() value queries. You need an equality operator, an overlaps(left, right) operation, a stream output operator and a merge operation that returns a new interval when the inputs overlap.
Classify each operation as a member, ordinary non-member or friend non-member. For every proposed friend, name the representation fact that the public queries cannot safely or adequately provide. Then consider a serialiser class with ten methods, only one of which needs exact private representation. Decide whether to befriend the class, befriend one function or expose a different stable operation. Your answer must preserve the interval invariant and explain how a later representation change affects each selected form.
Reveal answer
I will use closed integer intervals, so two intervals that meet at one endpoint overlap. Construction is the only route by which the two endpoints enter the object, and it rejects lower > upper.
#include <algorithm>
#include <optional>
#include <ostream>
#include <stdexcept>
class Interval {
public:
Interval(int lower, int upper)
: lower_{lower}, upper_{upper}
{
if (lower > upper) {
throw std::invalid_argument{"invalid interval"};
}
}
[[nodiscard]] int lower() const noexcept { return lower_; }
[[nodiscard]] int upper() const noexcept { return upper_; }
private:
int lower_ = 0;
int upper_ = 0;
};
bool operator==(const Interval& left, const Interval& right) noexcept
{
return left.lower() == right.lower()
&& left.upper() == right.upper();
}
bool overlaps(const Interval& left, const Interval& right) noexcept
{
return left.lower() <= right.upper()
&& right.lower() <= left.upper();
}
std::ostream& operator<<(std::ostream& out, const Interval& value)
{
return out << '[' << value.lower()
<< ", " << value.upper() << ']';
}
std::optional<Interval> merge(
const Interval& left,
const Interval& right)
{
if (!overlaps(left, right)) {
return std::nullopt;
}
return Interval{
std::min(left.lower(), right.lower()),
std::max(left.upper(), right.upper())};
}
All four operations are ordinary non-members. Equality is symmetric, overlap relates two equal-status operands, stream output has the stream as its left operand, and merge produces a third value rather than changing either input. None requires friendship because the public queries provide every semantic fact required. Granting private access would make these functions depend on storage without buying any capability.
| Operation | Selected form | Effect of changing the representation |
|---|---|---|
operator== | Ordinary non-member. | Unchanged while lower() and upper() retain their meanings. |
overlaps | Ordinary non-member. | Unchanged for the same reason. |
operator<< | Ordinary non-member. | Its public format remains stable even if storage changes. |
merge | Ordinary non-member. | It continues to construct through the checked constructor. |
For example, the class could later store a lower endpoint and a non-negative width. Only the constructor and the two queries would need to translate that representation. The four operations would continue to express the same interval behaviour and every merged result would still pass through the invariant check.
I would not befriend the ten-method serialiser class. That would authorise nine methods which have no stated need. The better design is a stable class operation such as to_record() that returns a deliberate serialisation record containing the semantic endpoints. The wire format then depends on a published record, not on the current member layout. If exact storage access were genuinely unavoidable, one narrowly declared serialisation function could be a friend, but the resulting representation coupling should be documented and tested. Class-wide friendship is not justified by one exceptional operation.
Friendship is a visible exception to an access boundary. Use it when the exception expresses a real non-member relationship that public behaviour cannot serve cleanly. If its only achievement is shorter typing around a private member, the code has gained privilege without gaining design.