14. 代理模式(Proxy):虚代理、保护代理、远程代理,C++智能指针中的代理思想

代理模式,说白了就是「找人替你办事」。你想想看,生活中我们经常这么干——找中介租房、让秘书安排会议、请律师打官司。自己不出面,但事情照样能办成。

在C++里,代理模式也是这个道理。一个对象不想直接暴露给客户端,或者直接操作成本太高,那就找个「替身」来挡在前面。这个替身就是Proxy,它和原对象实现同一个接口,客户端感知不到区别。

我个人习惯把代理模式分成三类:虚代理、保护代理、远程代理。咱们一个一个聊。

14.1 虚代理(Virtual Proxy)

虚代理解决的是「延迟加载」的问题。对象创建成本高,但又不一定马上用得到,那就先弄个轻量级的代理占个位,真正需要的时候再创建真实对象。

我在项目中遇到过一个大图像查看器。用户打开一个文件夹,里面几百张高清图,如果全部加载到内存,程序直接崩。解决方案就是虚代理——先显示缩略图,用户双击某张图时,才加载原图。

核心思想: 延迟创建,按需加载。
class Image {
public:
    virtual void display() = 0;
    virtual ~Image() = default;
};

class HighResImage : public Image {
    string filename;
public:
    HighResImage(const string& f) : filename(f) {
        loadFromDisk(); // 开销大
    }
    void display() override {
        cout << "Displaying " << filename << endl;
    }
    void loadFromDisk() {
        cout << "Loading " << filename << " from disk..." << endl;
    }
};

class ImageProxy : public Image {
    string filename;
    HighResImage* realImage = nullptr;
public:
    ImageProxy(const string& f) : filename(f) {}
    void display() override {
        if (!realImage)
            realImage = new HighResImage(filename);
        realImage->display();
    }
    ~ImageProxy() { delete realImage; }
};

你看,客户端始终操作的是Image*接口,根本不知道背后是代理还是真实对象。这就是代理模式的精髓——透明替换。

14.2 保护代理(Protection Proxy)

保护代理管的是「谁可以碰」。说白了就是权限控制。真实对象不想暴露给所有人,代理在中间做一道安检。

我曾经给一个内部管理系统写过权限模块。不同角色能看到的数据不一样,但底层数据接口是同一个。我不想在每个业务方法里都写if-else判断权限,太恶心了。于是用保护代理,在调用前统一拦截。

class Document {
public:
    virtual void view() = 0;
    virtual void edit() = 0;
    virtual ~Document() = default;
};

class RealDocument : public Document {
    string content;
public:
    RealDocument(const string& c) : content(c) {}
    void view() override {
        cout << "Content: " << content << endl;
    }
    void edit() override {
        cout << "Editing document..." << endl;
    }
};

class ProtectedDocument : public Document {
    RealDocument* doc;
    string userRole;
public:
    ProtectedDocument(const string& c, const string& role)
        : doc(new RealDocument(c)), userRole(role) {}
    void view() override {
        doc->view(); // 所有人都能看
    }
    void edit() override {
        if (userRole == "admin" || userRole == "editor") {
            doc->edit();
        } else {
            cout << "Access denied: you cannot edit." << endl;
        }
    }
    ~ProtectedDocument() { delete doc; }
};
注意: 保护代理不能替代服务端的安全校验。如果攻击者能直接拿到真实对象的指针,代理就形同虚设。保护代理适合客户端层面的权限控制,真正的安全防线还是在后端。

14.3 远程代理(Remote Proxy)

远程代理处理的是「不在同一个地址空间」的问题。对象在另一台机器上,或者另一个进程里,客户端想调用它就像调用本地对象一样。这其实就是RPC(远程过程调用)的雏形。

我记得早年做分布式系统时,用过CORBA和Java RMI,底层就是远程代理。C++里虽然没有内置的远程代理框架,但自己实现一个也不复杂——代理负责序列化请求、网络传输、反序列化响应。

class Calculator {
public:
    virtual int add(int a, int b) = 0;
    virtual ~Calculator() = default;
};

class RemoteCalcProxy : public Calculator {
    string serverAddress;
public:
    RemoteCalcProxy(const string& addr) : serverAddress(addr) {}
    int add(int a, int b) override {
        // 1. 序列化请求
        string request = serialize(a, b);
        // 2. 发送到 serverAddress
        string response = sendRequest(request);
        // 3. 反序列化结果
        return deserialize(response);
    }
};

当然,生产环境不建议自己造轮子。gRPC、Thrift这些框架已经做得很成熟了。但理解远程代理的原理,能帮你更好地理解这些框架的设计思路。

14.4 C++智能指针中的代理思想

聊到这里,你可能会觉得代理模式有点「重」。其实C++里天天用的智能指针,就是代理模式的经典应用。

std::shared_ptrstd::unique_ptr 本质上就是原始指针的代理。它们接管了内存管理的职责,让你不用手动delete。这不就是代理模式吗?

  • shared_ptr:引用计数代理。多个代理共享同一个真实对象,最后一个代理销毁时释放资源。
  • unique_ptr:独占代理。代理销毁时自动释放真实对象,不允许拷贝,只允许移动。
  • weak_ptr:弱引用代理。不增加引用计数,解决循环引用问题。
void process() {
    // 代理模式:shared_ptr 代理了原始指针
    std::shared_ptr<Image> proxy = std::make_shared<HighResImage>("photo.jpg");
    proxy->display();  // 客户端完全不知道内部有引用计数
    // 离开作用域,自动释放
}
我的建议: 写C++代码时,尽量用智能指针代替裸指针。这不只是内存安全的问题,更是一种设计思维的转变——你不再关心「什么时候释放」,而是把控制权交给代理。这就是代理模式带来的抽象价值。

14.5 代理模式的核心结构

下面这张图展示了代理模式的通用结构。你可以看到,ProxyRealSubject 都实现了同一个接口 Subject,客户端只跟 Subject 打交道。

Subject + request() Proxy - realSubject: RealSubject + request() // 控制访问、延迟加载等 RealSubject - data: ... + request() // 真正的业务逻辑 Client 只依赖 Subject 接口 实现 实现 持有引用 调用

14.6 代理模式 vs 装饰器模式

很多初学者会把代理模式和装饰器模式搞混。它们结构确实很像,但意图完全不同。

维度 代理模式 装饰器模式
核心目的 控制访问、延迟加载、权限校验 动态扩展功能
对象创建 代理通常自己创建或持有真实对象 装饰器接收外部传入的被装饰对象
接口透明性 完全透明,客户端无感知 透明,但可以叠加多层
典型场景 智能指针、远程调用、懒加载 流处理、日志、缓存

说白了,代理模式是「替身」,装饰器模式是「增强」。代理不想让你直接碰真实对象,装饰器则是在真实对象的基础上加料。

14.7 避坑指南

我曾经在一个项目里过度使用代理模式。每个接口都套一层代理,结果代码变得极其难调试。你想想看,调用链上多了好几层间接跳转,出问题根本不知道是代理层的问题还是真实对象的问题。

所以我的经验是:代理模式虽好,但别滥用。只有当你确实需要「控制访问」或「延迟加载」时,才引入代理。否则,保持简单直接。

总结一下:
  • 虚代理:延迟创建,按需加载
  • 保护代理:权限控制,统一拦截
  • 远程代理:跨地址空间调用
  • C++智能指针:内存管理的代理思想

代理模式的核心价值,就是让你在不修改真实对象的前提下,增加一层控制。这层控制可以是延迟、权限、网络,也可以是内存管理。理解了这个本质,你就能在合适的场景下灵活运用。

专注资料整理