StructuralC++verifiedVerified
Proxy Pattern in C++
Provides a surrogate or placeholder for another object to control access, add lazy initialization, caching, logging, or access control.
How to Implement the Proxy Pattern in C++
1Step 1: Define the Subject interface
class Image {
public:
virtual ~Image() = default;
virtual void display() = 0;
};2Step 2: Real subject (expensive to create)
class RealImage : public Image {
std::string filename_;
public:
explicit RealImage(std::string name) : filename_(std::move(name)) {
std::cout << "Loading " << filename_ << " from disk...\n";
}
void display() override {
std::cout << "Displaying " << filename_ << "\n";
}
};3Step 3: Proxy delays loading until display() is called
class ImageProxy : public Image {
std::string filename_;
std::unique_ptr<RealImage> real_;
public:
explicit ImageProxy(std::string name) : filename_(std::move(name)) {}
void display() override {
if (!real_) real_ = std::make_unique<RealImage>(filename_);
real_->display();
}
};
int main() {
ImageProxy img("photo.jpg");
std::cout << "Image created (not loaded yet)\n";
img.display(); // loads now
img.display(); // uses cached
}#include <iostream>
#include <string>
#include <memory>
#include <map>
#include <mutex>
#include <format>
#include <chrono>
#include <functional>
#include <optional>
#include <stdexcept>
// [step] Define the service interface
class IDatabase {
public:
virtual ~IDatabase() = default;
virtual std::string query(const std::string& sql) = 0;
virtual void execute(const std::string& sql) = 0;
};
// [step] Real database (expensive operations)
class RealDatabase : public IDatabase {
std::string connectionString_;
public:
explicit RealDatabase(std::string connStr) : connectionString_(std::move(connStr)) {
std::cout << std::format("Connecting to {}...\n", connectionString_);
}
std::string query(const std::string& sql) override {
return std::format("Result of: {}", sql);
}
void execute(const std::string& sql) override {
std::cout << std::format("Executed: {}\n", sql);
}
};
// [step] Caching proxy with TTL
class CachingProxy : public IDatabase {
std::unique_ptr<IDatabase> real_;
struct CacheEntry {
std::string result;
std::chrono::steady_clock::time_point expiresAt;
};
std::map<std::string, CacheEntry> cache_;
std::mutex mu_;
std::chrono::seconds ttl_;
public:
CachingProxy(std::unique_ptr<IDatabase> real, std::chrono::seconds ttl = std::chrono::seconds{60})
: real_(std::move(real)), ttl_(ttl) {}
std::string query(const std::string& sql) override {
std::lock_guard lk(mu_);
auto it = cache_.find(sql);
auto now = std::chrono::steady_clock::now();
if (it != cache_.end() && it->second.expiresAt > now) {
std::cout << std::format("[CACHE HIT] {}\n", sql.substr(0, 30));
return it->second.result;
}
auto result = real_->query(sql);
cache_[sql] = {result, now + ttl_};
return result;
}
void execute(const std::string& sql) override {
std::lock_guard lk(mu_);
cache_.clear(); // Invalidate cache on writes
real_->execute(sql);
}
};
// [step] Access control proxy
class AccessControlProxy : public IDatabase {
std::unique_ptr<IDatabase> real_;
std::string currentRole_;
static bool isWriteQuery(const std::string& sql) {
return sql.find("INSERT") == 0 || sql.find("UPDATE") == 0 ||
sql.find("DELETE") == 0 || sql.find("DROP") == 0;
}
public:
AccessControlProxy(std::unique_ptr<IDatabase> real, std::string role)
: real_(std::move(real)), currentRole_(std::move(role)) {}
std::string query(const std::string& sql) override {
return real_->query(sql);
}
void execute(const std::string& sql) override {
if (currentRole_ != "admin" && isWriteQuery(sql))
throw std::runtime_error(
std::format("Access denied: '{}' cannot execute writes", currentRole_));
real_->execute(sql);
}
};
// [step] Logging proxy
class LoggingProxy : public IDatabase {
std::unique_ptr<IDatabase> real_;
std::vector<std::string> log_;
public:
explicit LoggingProxy(std::unique_ptr<IDatabase> real)
: real_(std::move(real)) {}
std::string query(const std::string& sql) override {
log_.push_back(std::format("QUERY: {}", sql));
auto start = std::chrono::steady_clock::now();
auto result = real_->query(sql);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
log_.push_back(std::format(" -> {}ms", ms));
return result;
}
void execute(const std::string& sql) override {
log_.push_back(std::format("EXEC: {}", sql));
real_->execute(sql);
}
const std::vector<std::string>& getLog() const { return log_; }
};
// [step] Stack proxies for a production setup
int main() {
auto db = std::make_unique<RealDatabase>("postgres://localhost/mydb");
auto logged = std::make_unique<LoggingProxy>(std::move(db));
auto cached = std::make_unique<CachingProxy>(std::move(logged));
auto secured = std::make_unique<AccessControlProxy>(std::move(cached), "reader");
// Reads work
auto r1 = secured->query("SELECT * FROM users");
std::cout << r1 << "\n";
auto r2 = secured->query("SELECT * FROM users"); // cache hit
std::cout << r2 << "\n";
// Write denied for reader role
try {
secured->execute("DELETE FROM users WHERE id=1");
} catch (const std::exception& e) {
std::cout << "Caught: " << e.what() << "\n";
}
}Proxy Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Proxy Pattern in the Real World
“A corporate receptionist acts as a proxy for the CEO. When someone wants to meet the CEO, the receptionist checks credentials, schedules the meeting, and logs the visit before granting access. The visitor interacts with the receptionist using the same protocol they would use with the CEO—the receptionist simply adds control and record-keeping around that access.”