C++ 標準規定 operator=、operator->、operator[]、operator() 必須作為非靜態成員函數重載,不能 重載為友元函數或全局函數。這不是任意限制,而是為了避免語義衝突、保證對象語義一致性、支持鏈式調用 的必要設計決策。
#include
using namespace std;
class A {
private:
int x;
public:
A() { x = 99; }
A(int xx) {
cout << "Call A(int xx)" << endl;
x = xx;
}
};
int main() {
A a;
a = 7; // 這行代碼發生了什麼?
}
輸出: Call A(int xx)
解析:編譯器看到 a = 7,類 A 沒有定義 operator=(int),但有一個轉換構造函數 A(int)。C++ 規則允許隱式轉換構造函數將右值構造臨時對象,再調用默認拷貝賦值運算符 (逐成員拷貝)。等價於 a.operator=(A(7))。
class A {
// ... 同上 ...
A& operator=(int xx) { // 成員函數
cout << "Call A::operator=(int)" << endl;
x = xx;
return *this;
}
};
int main() {
A a;
a = 7; // 現在輸出什麼?
}
輸出: Call A::operator=(int)
結論:成員函數優先於隱式轉換+默認賦值。一旦定義了成員 operator=,編譯器不再考慮轉換構造函數路徑。
假設允許 friend A& operator=(A&, int) 作為友元:
A 內部沒有成員 operator=。operator=(A&, int)。a = 7 時,編譯器面臨二義性:
A(7) → 默認拷貝賦值。operator=(a, 7)。同理:operator->、operator[]、operator() 都有編譯器隱式生成的默認行為 (如指針解引用、下標訪問、函數調用),允許友元重載會產生相同的二義性衝突。
| 運算符 | 必須為成員函數的原因 | 典型返回類型 |
|---|---|---|
operator= | 賦值改變左操作數對象本身,需訪問私有成員;支持鏈式 a=b=c 需返回 *this 引用 | T& |
operator-> | 實現智能指針語義,鏈式調用 ptr->func() 需返回指針/對象引用;成員函數隱式 this 保證鏈式調用正確 | T* 或 類類型 |
operator[] | 下標訪問修改對象內部狀態,需 this 指針;支持 obj[i] = val 需返回左值引用 | T& (非 const) |
operator() | 函數調用語法 obj(args) 綁定對象狀態;函數對象模式核心,需攜帶 this | 任意 |
class String {
char* data;
size_t len;
public:
// 複製賦值
String& operator=(const String& other) {
if (this != &other) { // 自賦值檢查
delete[] data;
len = other.len;
data = new char[len + 1];
memcpy(data, other.data, len + 1);
}
return *this;
}
// 移動賦值 (C++11)
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
len = other.len;
other.data = nullptr;
other.len = 0;
}
return *this;
}
};
class Vector {
double* elem;
int sz;
public:
// 非 const 版本: 支持 v[i] = 3.14 (左值)
double& operator[](int i) {
if (i < 0 || i >= sz) throw out_of_range("Vector::operator[]");
return elem[i];
}
// const 版本: 支持 const Vector cv; cv[i] (右值)
double operator[](int i) const {
if (i < 0 || i >= sz) throw out_of_range("Vector::operator[] const");
return elem[i];
}
};
struct Adder {
int base;
Adder(int b) : base(b) {}
// 使對象可調用: adder(5) 相當於 adder.operator()(5)
int operator()(int x) const { return base + x; }
};
int main() {
Adder add10(10);
cout << add10(5) << endl; // 15 - 像函數一樣調用對象
// STL 算法大量使用: sort(v.begin(), v.end(), [](a,b){return a>b;});
}
template
class UniquePtr {
T* ptr;
public:
UniquePtr(T* p = nullptr) : ptr(p) {}
~UniquePtr() { delete ptr; }
// 關鍵: 返回指針,支持鏈式 -> 操作
T* operator->() const {
if (!ptr) throw runtime_error("Dereference null");
return ptr;
}
T& operator*() const { return *ptr; }
};
struct Foo { void bar() { cout << "Foo::bar()" << endl; } };
int main() {
UniquePtr p(new Foo());
p->bar(); // 編譯器展開為: p.operator->()->bar()
// 即: (p.operator->())->bar()
}
C++ 強制 operator=、operator->、operator[]、operator() 為非靜態成員函數,根本原因是:
this 指針訪問私有成員、保證鏈式調用正確性、支持左值語義。理解這一設計哲理,有助於在設計自定義類型時正確實現值語義、智能指針、容器、函數對象等核心 C++ 抽象機制。