代理类必须继承抽象基类(含纯虚函数)才能实现多态替换,否则无法被std::unique_ptr持有;模板代理不参与运行时多态,仅适用于编译期已知类型的轻量转发。

代理类必须继承接口或与目标对象有相同虚函数表
直接写一个“代理类”但不和被代理对象共享接口,运行时就无法多态替换——C++ 没有运行时类型擦除机制。所以真实代理必须基于抽象基类(含纯虚函数)或模板约束,否则 std::unique_ptr<subject></subject> 根本 hold 不住具体代理实例。
常见错误是让 Proxy 和 RealSubject 各自定义一套同名函数,却不共用基类:编译可能过,但传参、容器存储、依赖注入全崩。正确做法是先定义接口:
struct Subject {
virtual ~Subject() = default;
virtual void request() = 0;
};
然后让两者都继承它:
-
RealSubject实现request() -
Proxy也继承Subject,内部持有一个std::unique_ptr<subject></subject>或Subject&(注意生命周期!) - 代理的
request()里可加日志、权限检查、延迟初始化等逻辑,再转发给真实对象
智能指针管理 RealSubject 时要注意循环引用
如果 Proxy 用 std::shared_ptr<realsubject></realsubject> 持有目标,而 RealSubject 又反过来持有 std::weak_ptr<proxy></proxy>(比如回调场景),就极易触发循环引用导致内存泄漏。更隐蔽的是:某些代理实现会把自身 this 捕获进 lambda 并交给异步任务,lambda 内部又间接引用了 shared_ptr<realsubject></realsubject> ——这时 Proxy 和 RealSubject 都无法析构。
实操建议:
- 优先用
std::unique_ptr<subject></subject>或裸指针 + 明确所有权边界(如Proxy生命周期严格短于RealSubject) - 若必须用
shared_ptr,确保转发链中任何一环都不反向持有强引用;回调参数改用weak_ptr+lock()检查 - 用 AddressSanitizer +
-fsanitize=leak跑单元测试,专门验证代理对象析构时机
模板代理(如 std::reference_wrapper)不能替代虚函数代理
std::reference_wrapper<t></t> 或简单模板包装器(如 template<typename t> struct Proxy { T& t; };</typename>)看起来像代理,但它不参与多态分发——你无法把它塞进接受 std::unique_ptr<subject></subject> 的函数里。这类模板只是语法糖,适用于编译期已知类型的轻量转发,不是设计模式意义上的代理。
关键区别在于绑定时机:
- 虚函数代理:运行时决定调用哪个
request(),支持策略替换、AOP 风格增强 - 模板代理:所有调用在编译期内联,零开销但丧失动态性,也无法统一管理不同被代理对象
- 混用风险:有人试图用模板代理去 mock 接口做单元测试,结果发现 Google Mock 无法
EXPECT_CALL它的成员函数——因为没虚函数,mock 框架无从接管 vtable
代理对象的拷贝/移动语义容易被忽略
默认生成的拷贝构造函数会让两个 Proxy 实例指向同一个 RealSubject,如果其中一个析构时 delete 了目标,另一个再访问就是悬空指针。更麻烦的是移动语义:若 Proxy 内部用 std::unique_ptr 管理 RealSubject,却没显式定义移动构造函数,编译器生成的版本会把指针置空,导致移动后原对象失效、新对象为空——但编译不报错。
必须显式处理:
- 禁止拷贝(
Proxy(const Proxy&) = delete;) - 定义移动构造/赋值(
Proxy(Proxy&&)要接管unique_ptr所有权) - 若代理需共享目标对象,改用
std::shared_ptr并确认线程安全(shared_ptr的引用计数操作是原子的,但所指对象不是)
虚函数表本身不参与拷贝,但代理类的资源管理逻辑必须对称。漏掉移动语义,调试时最常表现为 “对象还在用,但内部指针已经是 nullptr”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











