In one sentence: Proxy provides a surrogate or placeholder for another object — same interface, standing in front — in order to control access to it.
Real-world analogy
A well-known actor has a phone number listed publicly, and it rings on their assistant's desk. Producers, journalists, charities, old classmates — every one of them dials that number, and from the caller's side nothing marks it as a stand-in. You ask a question, you get "yes, we can do Thursday." It behaves like reaching the actor because it is the way of reaching the actor.
What the assistant actually does is decide what happens next. Most calls never disturb the actor at all: the diary already answers them, or the answer is a polite no. Some get written down and raised later. A few — the ones that clear the bar — get put through, and only then does the actor's day get interrupted. A caller with no credentials simply never gets that far.
And the assistant can't act. Not a line. Everything the callers actually want lives entirely with the actor; the assistant's whole job is deciding whether, when and how the real thing gets involved. That's the line between this and a decorator, which adds something of its own to what it wraps.
Mapping the analogy to the pattern:
- The assistant → the Proxy, the surrogate that everyone reaches first.
- The actor → the RealSubject, who holds the capability the callers are really after.
- The one published number that answers the same way either way → the Subject interface, which is what makes the stand-in substitutable at all.
- Screening callers' credentials → a protection proxy; only waking the actor when a call earns it → a virtual proxy's lazy creation; noting every call in the diary → a smart reference's housekeeping.
- Anyone dialling the number → the Client, who never finds out which of the two picked up.
Another angle: a credit card. Same purchase, same shop counter, but it stands between you and the cash in the vault — checking the balance, logging the transaction, and deciding whether the real money moves at all.
The problem
Your photo gallery shows thumbnails of 500 images. Each HighResImage loads 12 MB from disk in its constructor.
- Startup is unbearable. Opening the gallery loads 6 GB before a single pixel appears, and the user will view maybe three of those images.
- You can't just delay the constructor. The gallery code expects real
Imageobjects it can calldisplay()on right now. - Adding
if (loaded)everywhere is worse. Every call site would need the same null check, and forgetting one crashes the app.
GoF's motivating example is exactly this, in a document editor: embedded images are expensive to create, opening a document should be fast, and most images aren't even on screen. The trick is that the rendering and formatting code must not know any optimization is happening.
The solution
Create a class that implements the same interface as the expensive object and holds a reference to it — initially empty:
- The proxy's constructor is cheap: it stores just the file path.
- On the first real method call, the proxy creates the real object and forwards to it.
- On every later call, the real object already exists, so the proxy just forwards.
Client code is unchanged — it still holds an Image* and calls display().
GoF names four kinds of proxy, and they're worth memorizing as a set, because they're the whole applicability list:
- Remote proxy — a local representative for an object living in a different address space. It encodes the request and its arguments and ships them across.
- Virtual proxy — creates an expensive object on demand. That's the one in the example below. A virtual proxy often caches cheap facts about the real subject (GoF's image proxy remembers the image's extent) so it can answer some questions without ever instantiating it.
- Protection proxy — controls access to the original object, checking that the caller has the required permissions. Useful when different callers should have different rights.
- Smart reference — a replacement for a bare pointer that does extra housekeeping on access: counting references so the object can be freed automatically, loading a persistent object into memory the first time it's touched, or verifying that the real object is locked before it's used.
Note: GoF's implementation section has one insight that is pure C++: you can overload
operator->so the proxy behaves like a pointer, without restating every operation. It comes with a real limitation, though — that approach fires on every dereference, so it can't tell one operation from another. When the proxy needs to act on a specific call (load the image onDraw, but not on a size query), you have to write each forwarding operation by hand. A second useful point: a proxy that only ever talks to its subject through an abstract interface doesn't need to know the concrete class, so one proxy can serve every subject class — but a virtual proxy, which has to instantiate the subject, does need the concrete type.
Structure
- Subject — the interface common to
RealSubjectandProxy, which is what lets a proxy be substituted anywhere the real subject is expected. In the example:Image. - RealSubject — the real object the proxy represents; it defines the actual functionality. In the example:
HighResImage. - Proxy — keeps a reference that lets it reach the real subject, offers an interface identical to
Subject, controls access to the real subject, and may be responsible for creating and deleting it. Its extra duties depend on which kind it is: encoding requests (remote), caching and postponing (virtual), checking permissions (protection). In the example:LazyImage. - Client — holds a
Subjectand never learns which it has.
C++ example
A lazy-loading image proxy — GoF's virtual proxy. Watch when the expensive load actually happens.
#include <iostream>
#include <memory>
#include <string>
#include <utility>
class Image {
public:
virtual ~Image() = default;
virtual void display() const = 0;
};
// The expensive real object: the work happens in its constructor.
class HighResImage : public Image {
public:
explicit HighResImage(std::string path) : path_(std::move(path)) {
std::cout << "[loading " << path_ << " from disk... 12 MB]\n";
}
void display() const override {
std::cout << "Showing " << path_ << "\n";
}
private:
std::string path_;
};
// The proxy: same interface, builds the real thing only on first use.
class LazyImage : public Image {
public:
explicit LazyImage(std::string path) : path_(std::move(path)) {}
void display() const override {
if (!real_) real_ = std::make_unique<HighResImage>(path_);
real_->display();
}
private:
std::string path_;
mutable std::unique_ptr<HighResImage> real_; // mutable: created inside a const method
};
int main() {
std::cout << "Building gallery...\n";
std::unique_ptr<Image> beach = std::make_unique<LazyImage>("beach.png");
std::unique_ptr<Image> mountain = std::make_unique<LazyImage>("mountain.png");
std::cout << "Gallery ready, nothing loaded yet.\n";
beach->display();
beach->display(); // already loaded: no second disk hit
std::cout << "mountain.png was never opened, so it was never loaded.\n";
}Building gallery...
Gallery ready, nothing loaded yet.
[loading beach.png from disk... 12 MB]
Showing beach.png
Showing beach.png
mountain.png was never opened, so it was never loaded.The load line appears once, after the gallery is already built, and never for the image nobody looked at — exactly the behavior the client code asked for without knowing it. Note how the proxy holds a file name until it holds the real object: GoF points out that a proxy referring to something not yet instantiated needs some address-space-independent identifier to name it by.
When should you use it?
GoF frames it broadly: use Proxy whenever you need a more versatile or sophisticated reference to an object than a plain pointer. In practice that means one of the four kinds:
- Remote proxy — the real object is in another address space or on another machine, and the proxy hides the encoding and the network hop behind an ordinary method call.
- Virtual proxy — the real object is expensive to create and often unused, so create it on demand.
- Protection proxy — different callers should have different access rights, and the check doesn't belong inside the real object.
- Smart reference — you want extra work on every access: reference counting, loading a persistent object on first touch, or asserting a lock is held.
Tip: Lazy creation inside a
constmethod needs amutablemember, as above. If several threads may calldisplay()at once, guard the initialization —std::call_onceor a mutex — or two threads will each load the image.
Pros and cons
Pros (GoF's consequences — all of them flow from one added level of indirection)
- A remote proxy hides the fact that an object lives in a different address space, so distribution stops leaking into the calling code.
- A virtual proxy can optimize invisibly — creating on demand, and answering cheap questions from cached data without instantiating anything.
- Protection proxies and smart references allow extra housekeeping on access — permission checks, reference counts, lock assertions — none of which the real class has to know about.
- Copy-on-write comes free from the same idea. Copying a heavyweight object is expensive and often pointless; a proxy over a reference-counted subject can make a "copy" just bump the count, and only pay for the real copy when someone writes.
- Client code is completely unchanged, and new concerns go in without touching the real class.
Cons
- One more class per proxied type, and more indirection when reading a stack trace.
- Responses can be delayed unpredictably: the first call pays a cost later calls don't.
- A protection proxy may refuse operations the subject would perform, so its interface can be effectively a subset of the subject's — clients that assume full substitutability get a surprise.
- Easy to accumulate layers of proxies until nobody knows what happens on a call.
How to remember it
Memory hook: "The credit card." Same interface as cash, same purchase — but it stands between you and the money, and it decides whether, when and how the real thing gets touched. The confusable twin is Decorator, and GoF's discussion draws the line cleanly: in Proxy, the subject provides the key functionality and the proxy grants or refuses access to it; in Decorator, the component provides only part of the functionality and the decorators supply the rest. That's why Decorator is built for recursive stacking while Proxy models one fixed relationship, usually known statically.
Related patterns
- Adapter — an adapter provides a different interface to the object it wraps; a proxy provides the same one (though a protection proxy may effectively expose a subset).
- Decorator — can look identical in code but differs in purpose: a decorator adds responsibilities, a proxy controls access. GoF notes proxies vary in how decorator-like they are — a protection proxy may be implemented exactly like one, while a remote proxy holds only an indirect reference to its subject.
- Facade — also simplifies access, but to a whole subsystem, and with a new interface rather than the same one.
- Flyweight — also avoids expensive object creation, but by sharing many identical objects rather than deferring one.