5.More C++ Idioms - Friendship and the Attorney-Client Idiom(友元与委托模式)

enter image description here

设想你是C++语言编程以及软件工程实践专家,一起聊聊C++开发的一些话题,最近在阅读More C++ idioms wikibook,里面有很多不错的关于C++开发应用的开发话题


ChatGPT 说: 《More C++ Idioms》是一本非常经典的 C++ 进阶书籍,深入探讨了 C++ 语言中的惯用法(idioms)。这些 idioms 体现了 C++ 的强大抽象能力,许多现代 C++ 代码库都可以看到它们的影子。


我们可以聊聊书中涉及的 C++ 设计模式、惯用法,或者它们在实际开发中的应用。你对哪些部分感兴趣?比如:

Curiously Recurring Template Pattern (CRTP)——用来实现静态多态、编译期优化等。 Type Erasure——用于实现泛型编程,比如 std::function 的底层原理。 Policy-based Design——像 std::allocator 这样的策略模式如何影响 STL 设计。 Small Object Optimization (SOO)——像 std::string 那样的小对象优化策略。 Non-Copyable 和 Empty Base Optimization (EBO)——提高 C++ 类的效率。 你最近对哪些 C++ 话题最感兴趣,或者在实际工程中遇到了什么有趣的 C++ 设计问题?


您说: 我们一个个惯用法来聊吧, 先从这个惯用法: Friendship and the Attorney-Client


ChatGPT 说: Friendship and the Attorney-Client Idiom(友元与委托模式) 概念 这个惯用法利用 C++ friend 关键字,使一个“Client”类只能通过一个“Attorney”类来访问某个“Server”类的私有或受保护成员。

它的核心思想是:

  • “Server”(服务器)类将某些权限授予一个“Attorney”类,而不直接暴露给所有客户端。
  • “Attorney”(律师)类有访问 Server 私有/受保护成员的权限,并在受控范围内提供服务。
  • “Client”(客户)只能通过“Attorney”来间接访问 Server。

这个惯用法的主要作用是精细化控制访问权限,在不破坏封装的情况下,实现更严格的访问管理。

#include <iostream>

// Server 类:只希望 Attorney 能访问其私有成员
class Server {
private:
    void sensitiveOperation() {
        std::cout << "Sensitive operation performed\n";
    }

public:
    // 让 Attorney 作为 friend,避免所有人直接访问 sensitiveOperation
    friend class Attorney;
};

// Attorney 类:受控地暴露 Server 的功能
class Attorney {
public:
    static void allowAccess(Server& server) {
        server.sensitiveOperation(); // 访问私有成员
    }
};

// Client 类:不能直接访问 Server,只能通过 Attorney 访问
class Client {
public:
    void doWork() {
        Server server;
        Attorney::allowAccess(server); // 只能通过 Attorney 调用
    }
};

int main() {
    Client client;
    client.doWork();
    return 0;
}

应用场景 精细化权限控制

  • 只让特定的“律师类”访问核心功能,而不直接暴露给所有用户。 例如,一个数据库类 Database 只允许 TransactionManager 提交事务,而不是所有代码都能调用 commit()。
  • 隐藏实现细节

让 Server 类的私有实现细节对普通用户不可见,增强封装性。 例如,std::allocator 可能使用类似的模式,让 std::vector 访问其内部 API,但不让普通用户直接访问。 - 模拟访问控制系统

在某些安全关键系统中,需要通过 Attorney 来决定 Client 是否有权限调用 Server 的某些功能。 - 对比传统 friend 的使用 很多人认为 friend 破坏了封装,但 Attorney-Client 方式是一个更好的封装方式:

传统 friend: - 直接把 friend 赋给 Client,但这样 Client 可能滥用权限。 - Attorney-Client Idiom: - 通过 Attorney 作为受控的访问通道,精确管理 Client 能执行哪些操作。 改进版本:模板实现 如果有多个 Server 类都需要类似的 Attorney 访问,可以用 模板 进行泛化:

template <typename ServerType>
class Attorney {
public:
    static void allowAccess(ServerType& server) {
        server.sensitiveOperation(); 
    }
};

class Server {
private:
    void sensitiveOperation() {
        std::cout << "Sensitive operation performed\n";
    }

    friend class Attorney<Server>; // 让特定 Attorney 访问
};

int main() {
    Server server;
    Attorney<Server>::allowAccess(server);
}

这样 Attorney 变成了一个通用的访问管理器,可以适配不同的 Server 类型。

总结 ✅ 优点:

  • 让 friend 更有针对性,避免破坏封装。
  • 允许精细化权限管理,减少暴露私有 API 的风险。
  • 易于扩展,比如结合模板进行通用化设计。 ❌ 缺点:

  • 增加了一层间接访问,可能会影响代码可读性。

  • 过度使用 friend 仍然可能导致难以维护的代码。
  • 适用场景: 适用于需要 严格权限控制、但又不想暴露内部实现的 C++ 设计,例如数据库管理器、事务控制器等。

下一步 如果你对这个惯用法的实际应用或优化有兴趣,我们可以继续探讨,比如:

  • 结合 std::unique_ptr 等智能指针管理访问权限
  • 在多继承结构下的使用方法
  • 现代 C++(C++11 及以后)如何改进这种模式 你觉得这个惯用法在你的 C++ 开发中是否有应用价值?🚀

否有应用价值?🚀

您说: 基本理解了这个概念,有两个关键点:1 使用friend这个语言特性,2 使用增加中间层类设计,透过中间类精细化控制


ChatGPT 说: 是的,你总结得很准确!Friendship and the Attorney-Client Idiom 主要基于两个核心设计点:

1️⃣ 使用 friend 语言特性 —— 让 Server 只允许 Attorney 访问其私有成员,而不直接对 Client 开放。 2️⃣ 增加中间层 Attorney —— 作为受控访问通道,确保 Client 只能通过 Attorney 访问 Server 的私有功能,从而实现更精细的访问控制。

进一步探讨

如果我们希望更灵活地管理访问权限,还可以引入策略模式(Policy-based Design),让不同 Attorney 类控制不同的权限。

例如:

#include <iostream>

// Server 类
class Server {
private:
    void sensitiveRead() { std::cout << "Reading sensitive data\n"; }
    void sensitiveWrite() { std::cout << "Writing sensitive data\n"; }

    // 让不同 Attorney 访问不同权限
    friend class ReadAttorney;
    friend class WriteAttorney;
};

// Attorney: 只允许读
class ReadAttorney {
public:
    static void read(Server& server) {
        server.sensitiveRead();
    }
};

// Attorney: 只允许写
class WriteAttorney {
public:
    static void write(Server& server) {
        server.sensitiveWrite();
    }
};

// Client
int main() {
    Server server;
    ReadAttorney::read(server);  // 只能读
    WriteAttorney::write(server); // 只能写
}

拓展思考 这样,我们可以创建多个 Attorney,分别管理不同的权限,而不是让一个 Attorney 访问所有功能。 这在权限管理、资源访问控制等场景中非常有用,比如: - FileReadAttorney 只允许读取文件; - FileWriteAttorney 只允许写入文件; - DatabaseTransactionAttorney 只允许提交事务; 甚至可以结合模板,将 Attorney 设计成可配置策略! 如果你感兴趣,我们可以继续深入探讨如何在现代 C++(C++11 及以后)中优化这种设计,比如结合 std::function 或 std::unique_ptr 来管理访问权限。🚀

暂且打住,不同模式先不用混杂在一起,为了应对更复杂得应用场景,多个设计模式组合在一起使用是常态,得逐渐依赖经验得积累


下一篇我们再讨论一下Policy-based desay模式

评论