A friend declaration inside a class grants a specific outside function or class permission to access that class's private and protected members. It exists because some operations need a tight coupling with a class's internals while still being implemented outside the class. The most common case is symmetric operators and stream output that have to read private fields. This lesson covers the four shapes of friend (free function, member function, class, and template friendship through forward declarations), where each one belongs, and where using friend is the wrong call.
friend Actually Doesfriend is a one-way grant of access. When a class says friend X, it tells the compiler "treat X as if its code lived inside this class for the purpose of accessing private and protected members." Nothing else changes. X isn't a member, it isn't inherited, and the class itself doesn't gain any access to X.
The function printReceipt is not a member of Order. It's a regular free function. The line friend void printReceipt(const Order& o); inside the class body is what lets it reach o.customerEmail and o.total even though those are private. Removing the friend declaration makes the same code fail to compile.
Three properties of friend to remember:
friend is friend regardless of which label it sits under.friend is a compile-time access check, not a runtime mechanism. There's no overhead at runtime compared to a regular function call.
The simplest case is a free function (a function that isn't a member of any class) declared as a friend. The declaration goes inside the class, and the definition lives outside.
Three details matter. First, inventoryValue is declared once inside the class with the friend keyword and defined once outside without it. Repeating friend in the definition would be a syntax error. Second, the function doesn't need to be qualified with Product:: because it isn't a member; it lives in the surrounding namespace. Third, when the function is called, it's called like any other free function: inventoryValue(mouse), not mouse.inventoryValue().
A common stylistic choice writes the body of a friend function directly inside the class body:
This is still a friend free function, not a member function. Defining it inside the class body is a shortcut that combines the declaration and definition. The function is still found through normal namespace lookup, and it still isn't called with the dot operator. The only difference is convenience: the body sits next to the data it touches.
The most common reason to use friend is to write operator<< for std::ostream. The access pattern is the part that matters here.
For std::cout << order; to work, the function signature has to be std::ostream& operator<<(std::ostream& os, const Order& o). The left operand is the stream, not the Order, so this function can't be a member of Order. It has to be a free function. And to print the private fields, it needs access. That's exactly the slot friend fills.
Without the friend declaration, operator<< would have to use public getters like o.getEmail(), o.getTotal(), and o.getItemCount(). That works, and many codebases do exactly that. The friend version is preferred when adding getters purely to make printing possible would be the alternative. The operator counts as part of the class's interface contract anyway, since the object is almost always printed the way the class wants it printed. Letting it see private members directly is honest about that relationship.
Symmetric binary operators have the same shape. To make 1.0 + price work alongside price + 1.0, the function operator+(double, const Price&) has to be a free function, and a friend declaration is the cleanest way to give it access. Friend free functions and binary operators go together.
When one class needs access to another class's private members, friendship can be granted at the class level instead of function by function.
friend class OrderAuditor; lets every member function of OrderAuditor reach private and protected members of Order. That's a broader grant than friending a single function. The trade-off: it declares that OrderAuditor and Order are tightly coupled by design, not just one function. Sometimes that's the right call (the two classes are conceptually one feature split across two types), and sometimes it's a sign the design needs rethinking.
Friend classes show up in patterns like:
The relationship visually:
The grant goes one way. OrderAuditor can see inside Order, but Order cannot see inside OrderAuditor. For bidirectional access, both classes must friend each other.
Sometimes a full friend-class grant is too broad, and only one specific method of another class needs access. C++ supports that too, but it requires careful ordering with forward declarations.
The friendship is targeted: only OrderProcessor::markAsShipped can touch Order::status directly. OrderProcessor::cancel, even though it lives in the same class, has no special access. This is the narrowest form of friendship and the most defensible one when it fits, because the access surface stays small.
The complication is the declaration order. Order mentions OrderProcessor::markAsShipped in its friend declaration, so the compiler has to know about that function by then. But OrderProcessor::markAsShipped takes an Order& parameter, so it has to know Order is a type. The pattern that solves this:
Order so OrderProcessor can name it in member signatures.OrderProcessor, leaving its member functions declared but not defined.Order, which can now name OrderProcessor::markAsShipped in the friend declaration.OrderProcessor members after Order is complete, so they can use Order's members.The wrong order produces compiler errors like 'Order' does not name a type or incomplete type 'Order' used in nested name specifier. The fix is always to identify which type the compiler needs to know about first, and forward-declare the other one.
Forward declarations and friend declarations are compile-time only. They don't add runtime overhead, but they do add complexity to header files and to the order in which translation units include things. For one-off friendships in a small codebase, this is fine. When the graph grows, friending whole classes is usually cleaner than friending dozens of individual members.
Beyond stream output, friend functions are useful for binary operators that should work the same way regardless of which side the class instance is on. Consider a Price class that wraps a double and needs both Price + double and double + Price to compile.
The member version Price::operator+(double) only matches when the left operand is a Price. It can never handle 2.00 + base because there's no implicit conversion that turns double into the object the member is called on. The free function with both operands explicit handles that case. Making it a friend lets it read p.amount directly instead of going through getAmount().
friend Is Not a Violation of EncapsulationIt's tempting to look at friend and think it pokes a hole in encapsulation. The pragmatic view is the opposite: friend is part of the class's declared interface contract, not a backdoor around it.
Encapsulation means controlling who can touch internal state. The class author decides who's trusted. A private: label keeps everyone out. A public: label lets everyone in. A friend declaration names specific functions or classes that get in. All three are deliberate choices the class author makes, all listed in the class body, all visible to anyone reading the header.
What friend is for:
operator<<, symmetric operator+) but need access to private state.What friend is not for:
A class with thirty friends is almost always a class with the wrong public interface. The right reaction is usually to widen the public API in a controlled way, or to merge the friend and the class into one type, not to keep adding friends. Treat friend as a precision tool for narrow cases, not a fallback when access controls feel inconvenient.
Two properties of friend cause confusion. Both matter.
Friendship is not inherited. If Base declares Helper as a friend, Helper does not automatically become a friend of Derived classes that inherit from Base. Each derived class has to renew the grant for the same access.
Helper was granted friendship by Base, so it can read Base::basePrivate. It can also read Base::baseValue on a Derived instance, because that field belongs to the Base portion of Derived. What it cannot do is read Derived::derivedPrivate, because Derived never declared Helper a friend. Friendship has to be granted by the class that owns the data.
(The point here is that the friend relationship doesn't follow inheritance.)
Friendship is not transitive. If A friends B, and B friends C, that does not make C a friend of A.
Each grant is independent. C++ deliberately keeps the relationship narrow so every type with special access to a class can be audited in one place. If transitivity were allowed, tracing through chains of friendships would be necessary to know who could touch the data.
When two classes reference each other, the order of declarations in the file matters. Forward declarations let the compiler know "a type with this name will be fully defined later" so it accepts pointers and references to it in the meantime.
The flow:
class Cart; is a forward declaration. It introduces the name Cart as a type, but doesn't say anything about its members. Pointers and references to Cart can be declared at this point, but a Cart can't be created and its members can't be accessed.class Checkout is fully defined, and its computeTotal member is declared (not defined) with a const Cart& parameter. The forward declaration is enough for the compiler to accept this signature.class Cart is fully defined, and inside it the friend declaration names Checkout::computeTotal. By now the compiler knows Checkout and its computeTotal member.Checkout::computeTotal comes after both classes are complete, so it can use c.subtotal from the now-complete Cart.The wrong order produces the typical g++ error invalid use of incomplete type 'class X'. The fix is to identify which member function needs the other class to be fully defined, and move its body below both class definitions.
For the friend-class form (friend class Cart;), the rules are looser. friend class Cart; inside Checkout works even if Cart has only been forward-declared, because friend declarations themselves introduce the name when nothing else has. But when the friend is a specific member function like Checkout::computeTotal, the compiler needs the function's declaration to already exist, which means Checkout has to be defined first.
10 quizzes