C++代理模式:原理、实现与工程实践
2026/9/22 1:19:13 网站建设 项目流程

1. 代理模式基础认知

第一次接触代理模式是在重构一个老旧日志系统时。原有模块直接调用文件IO导致性能卡顿,我尝试在调用路径中插入一个缓冲代理层,系统吞吐量直接提升了8倍。这种"中间人"的设计思想,正是代理模式的核心所在。

代理模式(Proxy Pattern)属于结构型设计模式,其本质是通过引入代理对象来控制对原始对象的访问。就像明星经纪人处理各种邀约一样,代理对象在客户端和目标对象之间扮演着中介角色。这种模式在C++中尤为常见,因为C++程序员对系统资源的控制有着近乎偏执的追求。

从实现层面看,代理模式需要三个关键角色:

  • Subject(抽象主题):定义真实主题和代理主题的共同接口
  • RealSubject(真实主题):真正执行业务逻辑的对象
  • Proxy(代理):持有真实主题的引用,提供与真实主题相同的接口

在C++中实现代理模式时,我们通常采用接口继承的方式。下面是最基础的UML类图实现:

class Subject { public: virtual ~Subject() = default; virtual void Request() = 0; }; class RealSubject : public Subject { public: void Request() override { std::cout << "RealSubject handles the request.\n"; } }; class Proxy : public Subject { private: RealSubject* real_subject_; public: Proxy(RealSubject* real_subject) : real_subject_(real_subject) {} void Request() override { if (this->CheckAccess()) { real_subject_->Request(); this->LogAccess(); } } bool CheckAccess() const { std::cout << "Proxy: Checking access...\n"; return true; } void LogAccess() const { std::cout << "Proxy: Logging the request time.\n"; } };

这个基础实现已经展示了代理模式的核心价值:在不修改原始类的情况下,通过中间层实现访问控制。但实际工程中,我们会根据场景需求选择不同的代理变体。

2. C++代理模式的五种典型实现

2.1 虚拟代理(延迟加载)

在开发3D建模软件时,我们处理过百万级面数的模型加载问题。直接实例化所有模型对象会导致内存爆炸,这时虚拟代理就派上用场了。其核心思想是延迟昂贵资源的创建,直到真正需要时。

class HeavyModel { public: HeavyModel() { std::cout << "Loading heavy 3D model...\n"; // 模拟耗时操作 std::this_thread::sleep_for(std::chrono::seconds(3)); } void Render() { std::cout << "Rendering model\n"; } }; class ModelProxy { HeavyModel* real_model_; std::string model_path_; public: ModelProxy(const std::string& path) : model_path_(path), real_model_(nullptr) {} ~ModelProxy() { delete real_model_; } void Render() { if (!real_model_) { real_model_ = new HeavyModel(); // 按需创建 } real_model_->Render(); } };

这种实现有几个关键注意点:

  1. 代理对象必须与真实对象保持相同的接口
  2. 要处理好资源释放问题(建议使用智能指针)
  3. 线程安全需要考虑(首次访问时的竞态条件)

2.2 保护代理(访问控制)

在金融交易系统中,我们使用保护代理来实现分级授权。比如普通操作员只能查询交易记录,而经理可以执行修改操作。

class TradeSystem { public: virtual void ViewRecords() = 0; virtual void ModifyRecords() = 0; }; class RealTradeSystem : public TradeSystem { public: void ViewRecords() override { /*...*/ } void ModifyRecords() override { /*...*/ } }; class OperatorProxy : public TradeSystem { RealTradeSystem* real_system_; public: OperatorProxy(RealTradeSystem* sys) : real_system_(sys) {} void ViewRecords() override { real_system_->ViewRecords(); } void ModifyRecords() override { throw std::runtime_error("Permission denied"); } };

实际项目中,我们通常会结合RBAC(基于角色的访问控制)模型来实现更复杂的权限管理。

2.3 远程代理(网络通信)

在分布式计算系统中,远程代理是连接不同节点的桥梁。以下是一个简化的RPC代理实现:

class Calculator { public: virtual int Add(int a, int b) = 0; }; class RemoteCalculatorProxy : public Calculator { std::string server_addr_; public: RemoteCalculatorProxy(const std::string& addr) : server_addr_(addr) {} int Add(int a, int b) override { // 构造网络请求 std::string request = std::to_string(a) + "+" + std::to_string(b); // 发送请求(简化版) std::cout << "Sending to " << server_addr_ << ": " << request << "\n"; // 模拟网络延迟 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 返回模拟结果 return a + b; } };

在真实场景中,我们会使用gRPC或Thrift等框架,但原理与此类似。远程代理隐藏了网络通信的复杂性,使客户端像调用本地对象一样使用远程服务。

2.4 智能指针代理

C++标准库中的std::shared_ptr本身就是代理模式的经典应用。我们可以实现一个简单的带引用计数的智能指针:

template<typename T> class SmartPtr { T* raw_ptr_; int* ref_count_; public: explicit SmartPtr(T* ptr = nullptr) : raw_ptr_(ptr), ref_count_(new int(1)) {} SmartPtr(const SmartPtr& other) : raw_ptr_(other.raw_ptr_), ref_count_(other.ref_count_) { ++(*ref_count_); } ~SmartPtr() { if (--(*ref_count_) == 0) { delete raw_ptr_; delete ref_count_; } } T& operator*() const { return *raw_ptr_; } T* operator->() const { return raw_ptr_; } };

这种代理实现了资源管理的自动化,是C++中RAII原则的典型体现。

2.5 缓存代理(记忆化)

在游戏开发中,我们使用缓存代理来优化昂贵的路径计算:

class PathFinder { public: virtual std::vector<Point> FindPath(Point start, Point end) = 0; }; class RealPathFinder : public PathFinder { public: std::vector<Point> FindPath(Point start, Point end) override { // 模拟昂贵计算 std::this_thread::sleep_for(std::chrono::seconds(1)); return {start, Point{(start.x+end.x)/2, (start.y+end.y)/2}, end}; } }; class CachedPathFinder : public PathFinder { RealPathFinder* real_finder_; std::map<std::pair<Point, Point>, std::vector<Point>> cache_; public: CachedPathFinder(RealPathFinder* finder) : real_finder_(finder) {} std::vector<Point> FindPath(Point start, Point end) override { auto key = std::make_pair(start, end); if (cache_.find(key) == cache_.end()) { cache_[key] = real_finder_->FindPath(start, end); } return cache_[key]; } };

缓存代理可以显著提升系统性能,但需要注意缓存一致性问题——当底层数据变化时,要及时失效相关缓存。

3. 现代C++中的代理模式进阶

3.1 使用std::function实现通用代理

C++11引入的std::function允许我们创建更灵活的代理:

class UniversalProxy { std::function<void()> pre_action_; std::function<void()> real_action_; std::function<void()> post_action_; public: template<typename Pre, typename Real, typename Post> UniversalProxy(Pre pre, Real real, Post post) : pre_action_(pre), real_action_(real), post_action_(post) {} void Execute() { if (pre_action_) pre_action_(); if (real_action_) real_action_(); if (post_action_) post_action_(); } }; // 使用示例 UniversalProxy proxy( []{ std::cout << "Pre-processing\n"; }, []{ std::cout << "Real action\n"; }, []{ std::cout << "Post-processing\n"; } ); proxy.Execute();

这种实现方式特别适合需要动态组合多个操作的场景。

3.2 可变参数模板代理

C++11的可变参数模板让代理接口更加通用:

template<typename Subject> class GenericProxy : public Subject { std::unique_ptr<Subject> real_subject_; public: template<typename... Args> GenericProxy(Args&&... args) : real_subject_(std::make_unique<Subject>(std::forward<Args>(args)...)) {} // 转发所有成员函数调用 template<typename R, typename... Args> R Call(R (Subject::*func)(Args...), Args&&... args) { std::cout << "Proxy: before call\n"; auto result = (real_subject_.get()->*func)(std::forward<Args>(args)...); std::cout << "Proxy: after call\n"; return result; } };

3.3 代理与移动语义

在现代C++中,正确处理移动语义对代理很重要:

class Resource { std::unique_ptr<char[]> data_; public: Resource(size_t size) : data_(new char[size]) {} Resource(Resource&&) = default; Resource& operator=(Resource&&) = default; void Process() { /*...*/ } }; class ResourceProxy { std::unique_ptr<Resource> resource_; public: ResourceProxy(size_t size) : resource_(std::make_unique<Resource>(size)) {} // 正确处理移动语义 ResourceProxy(ResourceProxy&&) = default; ResourceProxy& operator=(ResourceProxy&&) = default; void Process() { if (resource_) resource_->Process(); } };

4. 代理模式的工程实践与陷阱

4.1 性能考量

代理模式会引入额外的间接层,可能影响性能。在我们的性能测试中,简单代理调用的开销大约是直接调用的1.5-2倍。对于高频调用的热点路径,需要谨慎使用。

优化建议:

  • 将多个代理操作合并
  • 使用内联函数减少调用开销
  • 对于性能关键路径,考虑直接修改原始类

4.2 生命周期管理

在C++中,代理对象和真实对象的生命周期管理是个棘手问题。我们曾经遇到过代理对象比真实对象生命周期长导致的悬挂指针问题。

解决方案对比:

方案优点缺点
原始指针简单直接容易产生悬挂指针
shared_ptr自动管理可能产生循环引用
weak_ptr安全使用稍复杂
值语义最安全可能影响性能

推荐做法:

class SafeProxy { std::shared_ptr<RealSubject> subject_; public: explicit SafeProxy(std::shared_ptr<RealSubject> subject) : subject_(std::move(subject)) {} void Request() { if (subject_) { subject_->Request(); } } };

4.3 接口一致性维护

当真实对象的接口变更时,代理对象也需要同步更新。在大项目中,这可能导致维护负担。

我们采用的解决方案:

  1. 使用静态断言检查接口一致性
  2. 自动化接口测试
  3. 文档化代理契约
static_assert( std::is_same_v< decltype(&RealSubject::Request), decltype(&Proxy::Request) >, "Interface mismatch between Proxy and RealSubject" );

4.4 测试策略

代理对象的测试需要特别关注:

  1. 代理行为测试(是否正确地转发调用)
  2. 附加功能测试(如缓存、权限检查等)
  3. 异常场景测试(如网络断开、权限不足等)

我们常用的测试模式:

TEST(ProxyTest, ForwardsCallsToRealSubject) { auto mock = std::make_shared<MockSubject>(); EXPECT_CALL(*mock, Request()).Times(1); Proxy proxy(mock); proxy.Request(); } TEST(ProxyTest, ChecksAccessBeforeForwarding) { auto mock = std::make_shared<MockSubject>(); Proxy proxy(mock); ON_CALL(*mock, Request()).WillByDefault(Throw("Should not be called")); EXPECT_THROW(proxy.Request(), std::runtime_error); }

5. 代理模式与其他模式的联用

5.1 代理与装饰器模式

代理和装饰器模式结构相似但意图不同:

特性代理模式装饰器模式
目的控制访问增强功能
关系通常1:1可以嵌套
创建代理通常管理生命周期装饰器接收现有对象

实际项目中,我们曾结合两者实现安全日志功能:

// 代理控制访问 class SecureLoggerProxy : public LoggerInterface { LoggerInterface* logger_; AuthService& auth_; public: void Log(const std::string& msg) override { if (auth_.CheckPermission()) { logger_->Log(msg); } } }; // 装饰器添加功能 class TimestampLoggerDecorator : public LoggerInterface { LoggerInterface* wrapped_; public: void Log(const std::string& msg) override { auto now = std::chrono::system_clock::now(); wrapped_->Log(std::to_string(now.time_since_epoch().count()) + ": " + msg); } };

5.2 代理与桥接模式

在跨平台开发中,我们使用桥接模式分离抽象与实现,再用代理控制平台特定实现的访问:

// 桥接的抽象部分 class Window { protected: WindowImpl* impl_; public: virtual void Draw() = 0; }; // 代理控制实现访问 class WindowProxy : public Window { std::unique_ptr<WindowImpl> impl_; public: WindowProxy() { #ifdef WINDOWS impl_ = std::make_unique<WindowsWindowImpl>(); #else impl_ = std::make_unique<LinuxWindowImpl>(); #endif } void Draw() override { // 可能添加预处理 impl_->Draw(); } };

5.3 代理与观察者模式

在事件系统中,我们使用代理来控制观察者的访问:

class EventObserver { public: virtual void OnEvent(Event e) = 0; }; class RealObserver : public EventObserver { void OnEvent(Event e) override { /*...*/ } }; class ObserverProxy : public EventObserver { std::weak_ptr<RealObserver> observer_; public: void OnEvent(Event e) override { if (auto obs = observer_.lock()) { obs->OnEvent(e); } } };

这种结构既保证了观察者的安全访问,又避免了内存泄漏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询