代理类必须持有真实对象的指针或引用,禁用值拷贝;需继承公共虚基类以支持多态转发;代理生命周期不得早于真实对象;推荐用std::shared_ptr统一管理所有权与生命周期。

代理类必须持有真实对象的指针或引用
直接值拷贝真实对象会破坏代理语义——你代理的是“那个”对象,不是它的副本。尤其当真实对象有状态、资源(如文件句柄、网络连接)或重载了 operator= 时,值传递会导致行为错乱或资源重复释放。
实操建议:
- 用
std::unique_ptr<RealSubject>或RealSubject*持有(推荐前者,明确所有权) - 若需共享生命周期,用
std::shared_ptr<RealSubject>,但要警惕循环引用 - 避免裸指针 + 手动
new/delete—— 容易漏删、重复删,引发double free或悬空指针 - 构造代理时传入已存在的真实对象实例,不要在代理内部自行构造(除非是虚拟代理场景)
虚函数表和继承关系决定能否拦截调用
代理模式依赖多态:用户通过基类接口操作代理,代理再转发给真实对象。如果 RealSubject 没有从公共抽象基类(比如 Subject)继承,或关键方法没声明为 virtual,代理就无法统一接口,也就谈不上“透明代理”。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 编译报错
invalid static_cast或cannot convert from 'Proxy*' to 'Subject*' - 运行时调用的是代理自己的实现,而非真实对象的方法(因未覆写虚函数)
- 子类新增方法无法被代理识别(违反里氏替换)
实操建议:
- 定义纯虚基类
Subject,所有方法声明为virtual,至少含一个virtual ~Subject() = default; -
Proxy和RealSubject都公有继承Subject - 代理中覆写每个虚函数,显式调用
m_real->method(),别漏掉const重载版本
代理对象的生命周期不能早于真实对象结束
这是最隐蔽也最致命的问题:一旦真实对象析构了,代理还拿着它的指针/引用,后续任何转发调用都变成未定义行为(UB),轻则 crash,重则静默数据损坏。
使用场景中容易踩坑的情况:
- 真实对象是栈变量,代理在函数外继续使用(
RealSubject r; Proxy p{&r}; return p;) - 真实对象由局部
std::shared_ptr管理,但代理持有了裸指针 - 多线程环境下,真实对象被其他线程提前释放,代理无感知
实操建议:
- 强制代理与真实对象共用智能指针:把
std::shared_ptr<RealSubject>传给代理构造函数,并存为成员 - 若必须用裸指针,加注释说明“caller must ensure lifetime”,并在 debug 模式下用
assert(m_real != nullptr)做基础防护 - 避免在工厂函数中返回栈上代理对象绑定栈上真实对象的组合
std::shared_ptr 的线程安全边界要划清
很多人以为用了 std::shared_ptr 就天然线程安全——其实只保证引用计数原子性,不保证它指向的对象线程安全。代理转发调用时,若多个线程同时通过不同代理实例操作同一个 RealSubject,仍可能引发竞态。
性能与兼容性影响:
- 频繁拷贝
std::shared_ptr有轻微开销(原子增减引用计数),高频调用场景可考虑std::weak_ptr配合锁 - Windows MSVC 2015+ 和 GCC 5.0+ 对
std::shared_ptr的线程安全行为一致,但老编译器需查文档确认 - 若真实对象本身带锁(如
std::mutex成员),代理层不应再套一层锁,否则易死锁
实操建议:
- 代理转发前不做额外同步;同步责任落在
RealSubject实现内部 - 需要读写分离时,用
std::shared_ptr<const RealSubject>表达只读语义 - 调试时开启
-D_GLIBCXX_DEBUG(GCC)或_ITERATOR_DEBUG_LEVEL=2(MSVC)捕获悬空访问
代理模式真正难的从来不是写那几行转发代码,而是厘清谁拥有对象、谁负责销毁、谁来保证并发安全——这些边界一旦模糊,问题往往在压测或上线后才爆发,且难以复现。










