Passkey Idiom and Hidden Friends
The term “Passkey Idiom” caught my attention while I was revisiting “C++ Software Design” by Klaus Iglberger, in the section about testability design.
The friend keyword in C++ introduces coupling—often artificial—and is typically discouraged, even for testing purposes. However, the book highlights two exceptions where friend is appropriate:
- Idiomatic uses like the Passkey idiom
- Hidden friends (for ADL-friendly operator overloading).
Passkey Idiom
After researching online, I found the idea behind Passkey idiom is cool!
Instead of granting blanket access to all private members and methods, it provides friendship only at a functional level.
One common use case is when a class should only be created through a factory. The factory needs special access to the class’s constructor, while other code should be blocked from creating instances directly. This prevents bypassing important setup or tracking logic — like managing database connections or game objects in a scene.
In that case, we can make the constructor of the class private, but have the specified access through a key.
Consider a simple example below
class Connection {
public:
Connection(const uint16_t port, ConstructorKey): port_(port) {}
private:
void internalFunction(){};
uint16_t port_;
bool internal_flag_;
};
Instead of directly define friend class inside the Connectionclass, the idiom is to use indirection through the ConstructorKeyor the “passkey”.
class ConstructorKey {
friend class ConnectionFactory;
constexpr ConstructorKey(){};
};
Constructor of ConstructorKeyis purposely made private and only allow friend class ConnectionFactory to be able to call it.
Hence, the factory class would be able to use the passkey to instantiate Connectionobject without access to its private data or methods.
class ConnectionFactory {
public:
Connection createConnection(const uint16_t port) {
return Connection(port, {});
}
};
The usage pattern would typically appear as follows in application code:
ConnectionFactory factory;
Connection connection = factory.createConnection(1883);
The design explicitly prevents direct instantiation attempts:
Connection connection{123, {}};
// would fail to compile
// ConstructorKey::ConstructorKey()' is private within this context
Generalized Passkey Idiom
When the pass key idiom is generalized, it would look something like
class PasskeyBase { // deal with aggregate initialization madness
protected:
constexpr PasskeyBase() = default;
};
template <typename T>
class Passkey : PasskeyBase {
friend T;
constexpr Passkey() = default;
};
where PasskeyBaseadheres C++ Core guidelines
“C.35: A base class destructor should be either public and virtual, or protected and non-virtual”.
We can construct the keys using the template:
using ConstructorKey = Passkey<ConnectionFactory>;
using MethodKey = Passkey<OtherClass>;
// example of usage for method
// int OnlyFooCanAccess(Passkey<Foo>);
Important details
You might question why we need the base class. And that’s because of the “aggregate initialization madness”.
If without the PasskeyBaseor we simply change the previous definition of constructor ofConstructorKey to be
constexpr ConstructorKey() = default;
, the following would surprisingly compile:
Connection connection{123, {}};
because even though defaulted constructor are not accessible, it can be created via uniform initialization if it has no data members.
The key issue arises when the Passkey has no data members. While adding dummy data (as shown below) avoids the problem, this solution is not ideal since the dummy member remains indeterminate
template <typename T>
class Passkey {
friend T;
constexpr Passkey() = default;
int dummy_; // indeterminate
};
Beyond handling aggregate initialization, a production-ready Passkey implementation must also account for another edge case — preventing the following circumvention attempt:
ConstructorKey* hack = nullptr;
Connection hack_connection(80, *hack);
We should make it syntactically impossible to create ConstructorKey using dereferencing an uninitialized or null pointer, which is an undefined behaviour. Thus, we can either define or delete the copy constructor of ConstructorKey
constexpr ConstructorKey(const ConstructorKey& other) = default; // or delete
Although we can rely on compiler to delete the copy assignment operator and copy assignment constructor by defining either move assignment operator or move assignment constructor, in our case, we could follow Rules of Five to delete all since each Passkey should be unique by itself.
template <typename T>
class Passkey : PasskeyBase {
friend T;
constexpr Passkey() = default;
constexpr Passkey(const Passkey&) = delete; // copy assignment operator
constexpr Passkey& operator=(const Passkey&) = delete; // copy assignment constructor
constexpr Passkey(Passkey&&) noexcept = delete; // move assignment operator
constexpr Passkey& operator=(Passkey&&) noexcept = delete; // move assignment constructor
};
// reference
class ConstructorKey {
friend class ConnectionFactory;
constexpr ConstructorKey(){};
constexpr ConstructorKey(const ConstructorKey& other) = delete; // copy constructor
};
Hidden friend
For hidden friend, it’s just related to Argument-Dependent Lookup (ADL) which is a C++ rule that extends how the compiler searches for functions during overload resolution.
We should not pollute the global namespace by putting the friend function that overloads inside the struct or class such as:
class Coordinate {
public:
Coordinate(int x, int y) : x_(x), y_(y){};
friend std::ostream& operator<<(std::ostream& os,
const Coordinate& coordinate) {
return os << coordinate.x_ << " " << coordinate.y_;
}
private:
int x_;
int y_;
};
std::cout << Coordinate(2, 1) << std::endl;
Conclusion
Now you understand the good uses of friend – the Passkey idiom for controlled access and hidden friends for clean operator overloading.
Use them wisely, my friend!
If this piece was useful, consider following me on Medium to stay updated. Your support fuels my work. Thanks!
Is Your Friend in C++ a Good Friend? was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.

Pin Loon Lee | Sciencx (2025-05-02T00:44:16+00:00) Is Your Friend in C++ a Good Friend?. Retrieved from https://www.scien.cx/2025/05/02/is-your-friend-in-c-a-good-friend/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.