C++ 友元类与友元函数详解:打破封装的受控通道
📅 2026/7/25 17:44:00
👁️ 阅读次数
📝 编程学习
C++ 友元类与友元函数详解:打破封装的受控通道
一、引言:封装不是绝对的壁垒
C++ 的封装机制通过private和protected访问控制符保护类的内部成员不被外部随意访问。但在某些特殊场景下,两个紧密协作的类之间需要互相访问私有成员,或者某个函数需要访问多个类的内部细节。
友元(friend)机制正是为这种“受控的例外”而设计的。它允许你明确指定某些外部函数或类可以访问本类的私有和受保护成员,就像在封装之墙上开了一扇需要授权的门。
二、核心概念速览
| 维度 | 说明 |
| --- | --- |
| 友元函数 | 被授权访问类私有/保护成员的普通函数或成员函数 |
| 友元类 | 被授权访问类私有/保护成员的整个类 |
| 授予方向 | 类主动声明谁是它的友元(友元关系不可继承、不可传递) |
| 声明位置 | 在类定义的任意位置(private/protected/public 区域无影响) |
| 本质 | 对封装的一种受控的、显式的例外 |
| 设计原则 | 最小化使用:优先用公有接口,友元是最后手段 |
| 常见场景 | 运算符重载、紧密协作的类、工厂模式、单元测试 |
三、友元函数
3.1 全局函数作为友元
cpp复制下载
#include <iostream> #include <string> class Person { private: std::string name_; int age_; double salary_; // 敏感信息 public: Person(std::string name, int age, double salary) : name_(std::move(name)), age_(age), salary_(salary) { } // 声明友元函数:授权访问私有成员 friend void displayPersonInfo(const Person& p); friend void giveRaise(Person& p, double amount); }; // 友元函数的实现:可以访问 Person 的私有成员 void displayPersonInfo(const Person& p) { std::cout << "Name: " << p.name_ << ", Age: " << p.age_ << ", Salary: $" << p.salary_ // 直接访问私有成员 << std::endl; } void giveRaise(Person& p, double amount) { p.salary_ += amount; // 直接修改私有成员 std::cout << p.name_ << " got a raise of $" << amount << " (new salary: $" << p.salary_ << ")" << std::endl; } int main() { Person alice("Alice", 30, 75000); displayPersonInfo(alice); giveRaise(alice, 5000); }3.2 运算符重载中的典型应用
cpp复制下载
#include <iostream> class Complex { private: double real_, imag_; public: Complex(double r = 0, double i = 0) : real_(r), imag_(i) { } // 友元函数实现对称运算符(左侧操作数可能是非类类型) friend Complex operator+(const Complex& a, const Complex& b); friend Complex operator+(double a, const Complex& b); friend Complex operator+(const Complex& a, double b); // 友元函数实现流输出运算符 friend std::ostream& operator<<(std::ostream& os, const Complex& c); // 友元函数实现比较运算符 friend bool operator==(const Complex& a, const Complex& b); }; Complex operator+(const Complex& a, const Complex& b) { return Complex(a.real_ + b.real_, a.imag_ + b.imag_); } Complex operator+(double a, const Complex& b) { return Complex(a + b.real_, b.imag_); } Complex operator+(const Complex& a, double b) { return Complex(a.real_ + b, a.imag_); } std::ostream& operator<<(std::ostream& os, const Complex& c) { os << "(" << c.real_ << " + " << c.imag_ << "i)"; return os; } bool operator==(const Complex& a, const Complex& b) { return a.real_ == b.real_ && a.imag_ == b.imag_; } int main() { Complex c1(1.0, 2.0); Complex c2(3.0, 4.0); Complex sum1 = c1 + c2; // Complex + Complex Complex sum2 = 5.0 + c1; // double + Complex(必须用友元) Complex sum3 = c1 + 3.0; // Complex + double std::cout << "c1: " << c1 << std::endl; std::cout << "c2: " << c2 << std::endl; std::cout << "sum1: " << sum1 << std::endl; std::cout << "sum2: " << sum2 << std::endl; if (c1 == c2) { std::cout << "Equal" << std::endl; } else { std::cout << "Not equal" << std::endl; } }3.3 另一个类的成员函数作为友元
cpp复制下载
#include <iostream> #include <string> // 前置声明 class Database; class Logger { public: // 这个成员函数需要访问 Database 的私有数据 void logDatabaseState(const Database& db); }; class Database { private: std::string connectionString_; int activeConnections_; public: Database(std::string connStr, int connections) : connectionString_(std::move(connStr)), activeConnections_(connections) { } // 声明 Logger::logDatabaseState 为友元 friend void Logger::logDatabaseState(const Database& db); }; // 实现友元函数 void Logger::logDatabaseState(const Database& db) { std::cout << "[LOG] Database State:" << std::endl; std::cout << " Connection: " << db.connectionString_ << std::endl; // 私有成员 std::cout << " Active connections: " << db.activeConnections_ << std::endl; } int main() { Database db("mysql://localhost:3306/mydb", 42); Logger logger; logger.logDatabaseState(db); }四、友元类
4.1 基本用法
cpp复制下载
#include <iostream> #include <string> #include <vector> class BankAccount { private: std::string ownerName_; std::string accountNumber_; double balance_; std::vector<std::string> transactionHistory_; // 内部辅助函数 void recordTransaction(const std::string& description) { transactionHistory_.push_back(description); } public: BankAccount(std::string name, std::string number, double initialBalance) : ownerName_(std::move(name)), accountNumber_(std::move(number)), balance_(initialBalance) { } double getBalance() const { return balance_; } // 声明 BankManager 为友元类 friend class BankManager; }; // 友元类可以访问 BankAccount 的所有私有成员 class BankManager { public: static void deposit(BankAccount& account, double amount) { account.balance_ += amount; account.recordTransaction("Deposit: $" + std::to_string(amount)); } static bool withdraw(BankAccount& account, double amount) { if (amount <= account.balance_) { account.balance_ -= amount; account.recordTransaction("Withdraw: $" + std::to_string(amount)); return true; } return false; } static void printStatement(const BankAccount& account) { std::cout << "=== Account Statement ===" << std::endl; std::cout << "Owner: " << account.ownerName_ << std::endl; std::cout << "Account: " << account.accountNumber_ << std::endl; std::cout << "Balance: $" << account.balance_ << std::endl; std::cout << "--- Transactions ---" << std::endl; for (const auto& txn : account.transactionHistory_) { std::cout << " " << txn << std::endl; } } static void transfer(BankAccount& from, BankAccount& to, double amount) { if (withdraw(from, amount)) { deposit(to, amount); } } }; int main() { BankAccount alice("Alice", "A001", 5000); BankAccount bob("Bob", "B002", 3000); BankManager::deposit(alice, 1000); BankManager::withdraw(bob, 500); BankManager::transfer(alice, bob, 2000); std::cout << std::endl; BankManager::printStatement(alice); std::cout << std::endl; BankManager::printStatement(bob); }4.2 友元关系的特性:不可传递、不可继承
cpp复制下载
#include <iostream> class A { private: int secretA_ = 10; friend class B; // B 是 A 的友元 }; class B { protected: void accessA(const A& a) { std::cout << "B can see A's secret: " << a.secretA_ << std::endl; } }; class C : public B { public: void tryAccessA(const A& a) { // std::cout << a.secretA_ << std::endl; // 错误!友元关系不继承 // C 不是 A 的友元 } }; class D { public: void tryAccessA(const A& a) { // std::cout << a.secretA_ << std::endl; // 错误! // D 不是 A 的友元,即使 B 是,友元关系不传递 } }; int main() { A a; B b; // b.accessA(a); // 可以,但 accessA 是 protected,main 无法调用 }五、友元的使用场景
5.1 场景一:对称运算符重载(最常用)
cpp复制下载
class Vector3D { private: double x_, y_, z_; public: Vector3D(double x, double y, double z) : x_(x), y_(y), z_(z) { } // 友元函数:支持标量在运算符左侧 friend Vector3D operator*(double scalar, const Vector3D& v); friend Vector3D operator*(const Vector3D& v, double scalar); // 流输出 friend std::ostream& operator<<(std::ostream& os, const Vector3D& v); }; Vector3D operator*(double scalar, const Vector3D& v) { return Vector3D(scalar * v.x_, scalar * v.y_, scalar * v.z_); } Vector3D operator*(const Vector3D& v, double scalar) { return scalar * v; // 委托给上面的版本 } int main() { Vector3D v(1.0, 2.0, 3.0); Vector3D result1 = 2.5 * v; // 必须用友元函数 Vector3D result2 = v * 2.5; // 可以用成员函数,但友元更对称 }5.2 场景二:工厂模式
cpp复制下载
#include <memory> #include <iostream> class Product { private: // 构造函数私有:防止外部直接创建 Product(int id, const std::string& config) : id_(id), config_(config) { std::cout << "Product created: " << id_ << std::endl; } int id_; std::string config_; public: void use() { std::cout << "Using product " << id_ << " with config: " << config_ << std::endl; } // 工厂类是友元 friend class ProductFactory; }; class ProductFactory { public: static std::unique_ptr<Product> createProduct(int id, const std::string& config) { // 工厂可以访问 Product 的私有构造函数 return std::unique_ptr<Product>(new Product(id, config)); } static std::shared_ptr<Product> createShared(int id, const std::string& config) { return std::make_shared<Product>(id, config); // 需要友元 } }; int main() { auto p1 = ProductFactory::createProduct(1, "config_a"); p1->use(); auto p2 = ProductFactory::createShared(2, "config_b"); p2->use(); }5.3 场景三:单元测试访问私有成员
cpp复制下载
#include <iostream> #include <vector> #include <algorithm> class DataProcessor { private: std::vector<int> data_; int threshold_; bool processed_ = false; void internalSort() { std::sort(data_.begin(), data_.end()); } bool validateThreshold() { return threshold_ > 0 && threshold_ < 100; } public: DataProcessor(std::vector<int> data, int threshold) : data_(std::move(data)), threshold_(threshold) { } void process() { if (!validateThreshold()) { throw std::runtime_error("Invalid threshold"); } internalSort(); processed_ = true; } // 测试类作为友元 friend class DataProcessorTest; }; // 单元测试类 class DataProcessorTest { public: static void testInternalSort() { DataProcessor dp({5, 2, 8, 1, 9}, 10); // 测试内部排序 dp.internalSort(); // 可以访问私有方法 // 验证排序结果 assert(dp.data_[0] == 1); assert(dp.data_[4] == 9); std::cout << "testInternalSort: PASSED" << std::endl; } static void testValidateThreshold() { DataProcessor valid({1, 2, 3}, 50); assert(valid.validateThreshold() == true); // 访问私有方法 DataProcessor invalid({1, 2, 3}, 0); assert(invalid.validateThreshold() == false); std::cout << "testValidateThreshold: PASSED" << std::endl; } static void testProcess() { DataProcessor dp({3, 1, 2}, 50); dp.process(); assert(dp.processed_ == true); // 验证私有状态 assert(dp.data_ == std::vector<int>({1, 2, 3})); std::cout << "testProcess: PASSED" << std::endl; } }; int main() { DataProcessorTest::testInternalSort(); DataProcessorTest::testValidateThreshold(); DataProcessorTest::testProcess(); }六、友元 vs 非友元设计对比
cpp复制下载
// 方案一:使用友元(紧密耦合) class A { private: int data_; public: A(int d) : data_(d) { } friend class B; // B 可以直接访问 data_ }; class B { void process(const A& a) { int x = a.data_; // 直接访问 } }; // 方案二:使用公有接口(松耦合,推荐) class A { private: int data_; public: A(int d) : data_(d) { } int getData() const { return data_; } // 提供访问接口 }; class B { void process(const A& a) { int x = a.getData(); // 通过公有接口访问 } };七、友元使用原则
图表代码下载全屏
八、总结
友元机制是 C++ 封装体系中的“受控后门”,理解它需要把握以下要点:
- 友元的作用:授权特定的外部函数或类访问本类的私有和受保护成员。它是封装的例外机制,而不是封装的否定。
- 友元的特性:
- 不可传递:A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元
- 不可继承:父类的友元不会自动成为子类的友元
- 单向性:你声明谁是你的友元,谁才能访问你;你无权访问你的友元
- 典型应用场景:
- 运算符重载(最常见):实现对称的二元运算符
- 紧密协作的类对:如容器与迭代器、数据库与日志记录器
- 工厂模式:工厂类需要访问产品类的私有构造函数
- 单元测试:测试类需要访问被测试类的内部状态
- 设计原则:
- 友元是最后手段:优先考虑公有接口,其次是 protected,友元是不得已的选择
- 最小化友元权限:优先使用友元函数而非友元类
- 保持友元在同一模块内:友元关系应该限于紧密耦合、共同维护的类之间
- 用友元而非破坏封装:不要因为需要友元访问而将私有成员公开
正确使用友元,能够在保持封装性的同时实现必要的类间协作;滥用友元,则会破坏封装导致代码难以维护。友元就像封装之墙上的钥匙——交给值得信赖的邻居,而不是所有过路人。
编程学习
技术分享
实战经验