前言
friend 關鍵字讓非成員函式/類別存取私有成員,是 C++ 打破封裝的「後門」。本文系統剖析友元語法、適用場景、三大限制規則與最佳實踐,避免濫用導致維護災難。
一、 友元函式
語法與基本範例
class Point {
double x, y;
public:
Point(double xx, double yy) : x(xx), y(yy) {}
void print() const { std::cout << "(" << x << "," << y << ")"; }
// 宣告友元:非成員、可存取私有成員
friend double distance(const Point& a, const Point& b);
};
// 定義:像普通函式,無 Point:: 前綴
double distance(const Point& a, const Point& b) {
double dx = a.x - b.x; // 直接存取 private x
double dy = a.y - b.y;
return std::sqrt(dx*dx + dy*dy);
}
int main() {
Point p1(3,4), p2(6,8);
p1.print(); std::cout << " to "; p2.print();
std::cout << " distance = " << distance(p1, p2) << "\n";
return 0;
}
適用場景
- 雙向存取:
distance(a,b) 需同時存取兩物件私有資料,非單一物件成員函式職責。
- 運算子多載:
operator<<、operator+ (非成員對稱形式) 常需友元。
- 效能關鍵路徑:避免 getter/setter 呼叫開銷 (現代編譯器內聯通常可消除差異)。
二、 友元類別
語法與效果
class B; // 前置宣告
class A {
int secret;
friend class B; // B 所有成員函式皆為 A 的友元
public:
A() : secret(42) {}
};
class B {
public:
void reveal(const A& a) { std::cout << a.secret; } // 存取 A::secret
};
三大鐵律 (必須背誦)
| 規則 | 說明 | 程式碼驗證 |
| 不可繼承 | B 是 A 友元 ⇒ B 的子類別 不自動 成為 A 友元 | class C : public B { void f(A& a) { a.secret; } }; → 編譯錯誤 |
| 單向性 | B 是 A 友元 ⇏ A 是 B 友元 | A 無法存取 B::private,除非 B 也 friend class A; |
| 無傳遞性 | B 是 A 友元,C 是 B 友元 ⇏ C 是 A 友元 | 需在 A 明確 friend class C; |
三、 繼承與友元的微妙互動
class Base {
int base_secret;
friend class Derived; // Derived 可存取 Base::base_secret
};
class Derived : public Base {
int derived_secret;
// Derived 方法可存取 base_secret
// 但 Base 方法不可存取 derived_secret
};
總結表
| 存取方向 | 可存取 | 備註 |
| Derived 新增方法 → Base::private | ❌ | 友元關係不繼承 |
| Derived 繼承方法 → Base::private | ✅ | Base 方法本身即可存取 |
| Base 方法 → Derived::private (新增) | ❌ | Base 不知 Derived 細節 |
| Base 方法 → Derived::private (繼承自 Base) | ✅ | 實際是 Base 自身成員 |
四、 進階應用:友元函式模板
template
class Matrix {
T* data;
int rows, cols;
// 宣告特定實例化為友元
friend std::ostream& operator<<(std::ostream&, const Matrix&);
friend std::ostream& operator<<(std::ostream&, const Matrix&);
};
五、 最佳實踐與替代方案
- 最小權限原則:優先用
public getter/setter、protected 繼承存取、公開介面設計。
- 僅在必要時使用:運算子多載、緊密協作類別 (如容器與迭代器)、單元測試存取私有狀態。
- 文檔化意圖:註解說明
// friend for operator<<。
- 替代方案:
public 介面 + protected 給子類別
- Pimpl 惯用法隱藏私有細節
- 測試用
FRIEND_TEST 宏 (Google Test)
六、 常見誤區
| 誤區 | 真相 |
| 友元破壞封裝,絕對不能用 | C++ 設計者刻意保留,解決「雙向存取」與「運算子對稱」問題 |
| 友元是成員函式 | 友元是非成員,定義時無 Class:: 前綴 |
| 友元類別會自動繼承 | 不繼承、不傳遞、單向 |
| 模板類別無法宣告友元 | 可宣告特定實例化或模板友元 |
結語
friend 是 C++ 雙面刃:用得好能實現優雅的運算子多載與緊密協作;濫用則讓封裝形同虛設。牢記「不可繼承、單向、無傳遞」三鐵律,遵循「最小權限、文檔化、有替代方案優先用替代」原則,方能駕馭此高階特性。