尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C++代理模式:原理、实现与工程实践

C++代理模式:原理、实现与工程实践 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(); } };这种实现有几个关键注意点代理对象必须与真实对象保持相同的接口要处理好资源释放问题建议使用智能指针线程安全需要考虑首次访问时的竞态条件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本身就是代理模式的经典应用。我们可以实现一个简单的带引用计数的智能指针templatetypename 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::vectorPoint FindPath(Point start, Point end) 0; }; class RealPathFinder : public PathFinder { public: std::vectorPoint FindPath(Point start, Point end) override { // 模拟昂贵计算 std::this_thread::sleep_for(std::chrono::seconds(1)); return {start, Point{(start.xend.x)/2, (start.yend.y)/2}, end}; } }; class CachedPathFinder : public PathFinder { RealPathFinder* real_finder_; std::mapstd::pairPoint, Point, std::vectorPoint cache_; public: CachedPathFinder(RealPathFinder* finder) : real_finder_(finder) {} std::vectorPoint 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实现通用代理C11引入的std::function允许我们创建更灵活的代理class UniversalProxy { std::functionvoid() pre_action_; std::functionvoid() real_action_; std::functionvoid() post_action_; public: templatetypename 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 可变参数模板代理C11的可变参数模板让代理接口更加通用templatetypename Subject class GenericProxy : public Subject { std::unique_ptrSubject real_subject_; public: templatetypename... Args GenericProxy(Args... args) : real_subject_(std::make_uniqueSubject(std::forwardArgs(args)...)) {} // 转发所有成员函数调用 templatetypename R, typename... Args R Call(R (Subject::*func)(Args...), Args... args) { std::cout Proxy: before call\n; auto result (real_subject_.get()-*func)(std::forwardArgs(args)...); std::cout Proxy: after call\n; return result; } };3.3 代理与移动语义在现代C中正确处理移动语义对代理很重要class Resource { std::unique_ptrchar[] data_; public: Resource(size_t size) : data_(new char[size]) {} Resource(Resource) default; Resource operator(Resource) default; void Process() { /*...*/ } }; class ResourceProxy { std::unique_ptrResource resource_; public: ResourceProxy(size_t size) : resource_(std::make_uniqueResource(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_ptrRealSubject subject_; public: explicit SafeProxy(std::shared_ptrRealSubject subject) : subject_(std::move(subject)) {} void Request() { if (subject_) { subject_-Request(); } } };4.3 接口一致性维护当真实对象的接口变更时代理对象也需要同步更新。在大项目中这可能导致维护负担。我们采用的解决方案使用静态断言检查接口一致性自动化接口测试文档化代理契约static_assert( std::is_same_v decltype(RealSubject::Request), decltype(Proxy::Request) , Interface mismatch between Proxy and RealSubject );4.4 测试策略代理对象的测试需要特别关注代理行为测试是否正确地转发调用附加功能测试如缓存、权限检查等异常场景测试如网络断开、权限不足等我们常用的测试模式TEST(ProxyTest, ForwardsCallsToRealSubject) { auto mock std::make_sharedMockSubject(); EXPECT_CALL(*mock, Request()).Times(1); Proxy proxy(mock); proxy.Request(); } TEST(ProxyTest, ChecksAccessBeforeForwarding) { auto mock std::make_sharedMockSubject(); 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_ptrWindowImpl impl_; public: WindowProxy() { #ifdef WINDOWS impl_ std::make_uniqueWindowsWindowImpl(); #else impl_ std::make_uniqueLinuxWindowImpl(); #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_ptrRealObserver observer_; public: void OnEvent(Event e) override { if (auto obs observer_.lock()) { obs-OnEvent(e); } } };这种结构既保证了观察者的安全访问又避免了内存泄漏。
返回列表