1. 友元函数:突破封装的特殊权限
1.1 运算符重载的困境与解决方案
在C++中重载流插入运算符<<时,我们遇到了一个典型的设计难题。当尝试将其作为成员函数重载时,会导致不符合直觉的调用方式:
cpp复制class Date {
public:
// 成员函数重载导致调用顺序反人类
ostream& operator<<(ostream& _cout) {
_cout << _year << "-" << _month << "-" << _day;
return _cout;
}
};
int main() {
Date d(2024,5,20);
d << cout; // 反人类的调用方式
}
这种设计违背了C++流式操作的习惯用法。根本原因在于成员函数的隐式this指针机制——编译器会将d << cout转换为d.operator<<(cout),使得Date对象必须作为左操作数。
关键提示:流运算符重载必须保持
cout << obj的标准形式,这意味着它不能是成员函数,但又需要访问类的私有成员——这正是友元函数的用武之地。
1.2 友元函数的实现机制
通过将全局运算符函数声明为类的友元,我们完美解决了这一矛盾:
cpp复制class Date {
friend ostream& operator<<(ostream& _cout, const Date& d);
// 允许全局函数访问私有成员
private:
int _year, _month, _day;
};
// 全局函数定义
ostream& operator<<(ostream& _cout, const Date& d) {
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
这种实现具有以下关键特性:
- 保持标准调用语法:
cout << d - 可以访问Date的所有私有成员
- 支持链式调用:
cout << d1 << d2
1.3 友元函数的深入特性
访问权限的灵活性
友元声明不受类访问限定符影响,以下三种声明方式完全等效:
cpp复制class Example {
public: // 三种位置均可
friend void publicFriend();
protected:
friend void protectedFriend();
private:
friend void privateFriend();
};
多类友元关系
一个函数可以同时成为多个类的友元,实现跨类数据访问:
cpp复制class BankAccount; // 前向声明
class User {
private:
string _name;
friend void showUserAccount(const User&, const BankAccount&);
};
class BankAccount {
private:
double _balance;
friend void showUserAccount(const User&, const BankAccount&);
};
// 可访问两个类的私有成员
void showUserAccount(const User& u, const BankAccount& acc) {
cout << u._name << "的余额:" << acc._balance;
}
模板友元的特殊语法
当友元函数是模板时,需要特别注意声明语法:
cpp复制template<typename T>
class Container {
// 声明模板友元函数
template<typename U>
friend void peek(const Container<U>&);
private:
T _data;
};
template<typename T>
void peek(const Container<T>& c) {
cout << c._data; // 访问私有成员
}
2. 友元类:类间的特权访问
2.1 基本实现与应用场景
友元类允许另一个类的所有成员函数访问本类的私有成员,这种关系在需要紧密协作的类之间非常有用:
cpp复制class LinkedList {
private:
struct Node {
int data;
Node* next;
};
Node* _head;
public:
friend class LinkedListIterator; // 授予迭代器完全访问权
};
class LinkedListIterator {
public:
LinkedListIterator(const LinkedList& list) : _current(list._head) {}
// 可以直接操作LinkedList的私有成员
private:
LinkedList::Node* _current;
};
典型应用场景包括:
- 容器与迭代器的关系
- 工厂类与被创建类
- 紧密耦合的子系统组件
2.2 友元关系的三大特性
单向性(非对称)
友元关系就像单相思——A把B当朋友,但B不一定把A当朋友:
cpp复制class A {
friend class B; // B是A的友元
private:
int _a_secret;
};
class B {
private:
int _b_secret;
};
void test() {
A a;
B b;
// b._b_secret = 1; // 错误!A不是B的友元
// 即使在B的成员函数中也不能访问A的私有成员
}
不可传递性
友元关系不会"传染"——A是B的朋友,B是C的朋友,不意味着A是C的朋友:
cpp复制class C; // 前向声明
class B {
friend class A;
private:
int _b_data;
};
class A {
public:
void accessB(B& b) { b._b_data = 1; } // 可以
};
class C {
friend class B;
private:
int _c_data;
};
void test() {
A a;
B b;
C c;
// a.accessC(c); // 错误!A不能通过B访问C
}
不可继承性
友元关系不会遗传给派生类:
cpp复制class Base {
friend class Friend;
private:
int _base_data;
};
class Derived : public Base {
private:
int _derived_data;
};
class Friend {
public:
void access(Base& b) { b._base_data = 1; } // 可以
void access(Derived& d) {
// d._derived_data = 2; // 错误!不能访问派生类特有成员
}
};
2.3 实际工程中的应用案例
树形结构的实现
cpp复制class TreeNode {
friend class Tree; // 树类需要完全控制节点
private:
int _value;
TreeNode* _left;
TreeNode* _right;
TreeNode* _parent;
// 私有化构造函数,强制通过Tree类创建
TreeNode(int val) : _value(val), _left(nullptr), _right(nullptr), _parent(nullptr) {}
};
class Tree {
public:
void insert(int value) {
TreeNode* newNode = new TreeNode(value);
// 可以直接操作TreeNode的所有私有成员
if (!_root) _root = newNode;
else _insert(_root, newNode);
}
private:
void _insert(TreeNode* parent, TreeNode* node) {
if (node->_value < parent->_value) {
if (!parent->_left) {
parent->_left = node;
node->_parent = parent;
} else {
_insert(parent->_left, node);
}
} else {
// 类似处理右子树...
}
}
TreeNode* _root = nullptr;
};
工厂模式实现
cpp复制class Product {
friend class ProductFactory;
private:
Product() {} // 私有化构造函数
string _serial;
void generateSerial() { _serial = "PROD-" + to_string(rand()); }
};
class ProductFactory {
public:
static Product* create() {
Product* p = new Product();
p->generateSerial(); // 调用私有方法
return p;
}
};
3. 内部类:类中的类
3.1 内部类的基本特性
内部类是定义在另一个类内部的完全独立类,具有以下关键特点:
cpp复制class Outer {
private:
static int _outer_static;
int _outer_data;
public:
class Inner { // 内部类定义
public:
void accessOuter(const Outer& outer) {
cout << _outer_static; // 直接访问外部类静态成员
cout << outer._outer_data; // 通过对象访问实例成员
}
};
};
int Outer::_outer_static = 100;
访问权限的嵌套规则
内部类可以定义在外部类的任何访问区域:
cpp复制class Container {
public:
class PublicInner {}; // 公有内部类
protected:
class ProtectedInner {}; // 受保护内部类
private:
class PrivateInner {}; // 私有内部类
};
// 使用示例
Container::PublicInner pub; // OK
// Container::ProtectedInner prot; // 错误
// Container::PrivateInner priv; // 错误
3.2 内部类与外部类的关系
友元关系的单向性
内部类自动成为外部类的友元,但反之不成立:
cpp复制class Outer {
class Inner {
public:
void accessOuter(Outer& o) {
o._secret = 42; // 可以访问外部类私有成员
}
private:
int _inner_secret;
};
void accessInner() {
Inner i;
// i._inner_secret = 1; // 错误!外部类不是内部类的友元
}
private:
int _secret;
};
内存布局的独立性
内部类不影响外部类的大小:
cpp复制class Outer {
int x;
class Inner {
double a, b, c;
};
};
cout << sizeof(Outer); // 输出4(仅包含int x)
cout << sizeof(Outer::Inner); // 输出24(三个double)
3.3 内部类的典型应用
迭代器模式的实现
cpp复制class LinkedList {
private:
struct Node {
int data;
Node* next;
};
Node* _head;
public:
class Iterator {
public:
Iterator(Node* node) : _current(node) {}
int& operator*() { return _current->data; }
Iterator& operator++() { _current = _current->next; return *this; }
bool operator!=(const Iterator& other) { return _current != other._current; }
private:
Node* _current;
};
Iterator begin() { return Iterator(_head); }
Iterator end() { return Iterator(nullptr); }
};
// 使用示例
for (LinkedList::Iterator it = list.begin(); it != list.end(); ++it) {
cout << *it << endl;
}
实现细节的完全隐藏
cpp复制class Database {
private:
class ConnectionImpl { // 隐藏实现细节
string _connectionString;
bool _isConnected;
void realConnect() { /* 复杂实现 */ }
};
ConnectionImpl* _conn;
public:
Database(const string& connStr) {
_conn = new ConnectionImpl(connStr);
}
bool connect() {
return _conn->realConnect();
}
};
4. 类与对象的本质再认识
4.1 从现实到代码的抽象过程
洗衣机案例的完整抽象
cpp复制class WashingMachine {
private:
// 状态变量
enum State { IDLE, WASHING, RINSING, SPINNING };
State _state;
// 配置参数
int _waterLevel;
int _temperature;
int _spinSpeed;
// 硬件控制方法
void _startMotor() { /* 底层控制 */ }
void _openValve() { /* 底层控制 */ }
public:
// 用户接口
void startWashCycle() {
_openValve();
_startMotor();
_state = WASHING;
}
void setTemperature(int temp) {
if (temp < 20 || temp > 90) throw invalid_argument("温度超出范围");
_temperature = temp;
}
};
4.2 类设计的七个关键原则
- 单一职责原则:洗衣机类不应处理支付逻辑
- 开闭原则:通过继承扩展功能而非修改原有类
- 里氏替换原则:派生类应该可以替换基类
- 接口隔离原则:细分大型接口为多个专用接口
- 依赖倒置原则:依赖抽象而非具体实现
- 迪米特法则:减少类之间的耦合
- 高内聚低耦合:友元慎用,内部类合理使用
4.3 对象生命周期的深入理解
构造与析构的对称性
cpp复制class ResourceHolder {
public:
ResourceHolder() {
_fd = open("/dev/device", O_RDWR);
if (_fd == -1) throw runtime_error("打开设备失败");
}
~ResourceHolder() {
if (_fd != -1) close(_fd);
}
private:
int _fd;
};
RAII原则的应用
cpp复制class MutexLock {
public:
explicit MutexLock(pthread_mutex_t& mutex) : _mutex(mutex) {
pthread_mutex_lock(&_mutex);
}
~MutexLock() {
pthread_mutex_unlock(&_mutex);
}
private:
pthread_mutex_t& _mutex;
};
// 使用示例
void criticalSection() {
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
MutexLock lock(mutex); // 自动加锁
// ...操作共享资源...
} // 自动解锁
5. 友元与内部类的工程实践
5.1 性能敏感场景下的应用
矩阵运算的优化
cpp复制class Matrix {
friend class Vector; // 允许直接访问数据提升性能
private:
double* _data;
size_t _rows, _cols;
public:
// ...其他接口...
};
class Vector {
public:
Vector multiply(const Matrix& mat) {
Vector result;
// 直接访问Matrix的私有_data指针
for (size_t i = 0; i < mat._rows; ++i) {
for (size_t j = 0; j < mat._cols; ++j) {
result._data[i] += mat._data[i * mat._cols + j] * _data[j];
}
}
return result;
}
};
5.2 测试专用友元
cpp复制class ProductionClass {
friend class ProductionClassTest; // 仅对测试类开放
private:
int _internalState;
public:
// ...正常接口...
};
class ProductionClassTest : public ::testing::Test {
protected:
void checkInternalState(ProductionClass& obj) {
ASSERT_GT(obj._internalState, 0); // 直接验证私有状态
}
};
5.3 设计模式中的应用
观察者模式的内部类实现
cpp复制class Subject {
private:
class ObserverImpl : public ObserverInterface {
Subject* _parent;
public:
explicit ObserverImpl(Subject* s) : _parent(s) {}
void update() override {
// 可以直接访问Subject的私有成员
_parent->_notifyAll();
}
};
void _notifyAll() { /* 实现细节 */ }
ObserverImpl _observer;
public:
Subject() : _observer(this) {}
ObserverInterface& getObserver() {
return _observer;
}
};
在实际工程中,我经常发现开发者在设计紧密协作的类关系时,要么过度使用友元破坏封装,要么因噎废食导致接口冗余。一个实用的经验法则是:当两个类的关系如同"连体婴"般密不可分时,使用友元或内部类往往比通过复杂接口暴露私有状态更合理。特别是在性能关键的底层代码中,这种设计可以避免大量不必要的接口调用开销。