原文:http://blog.csdn.net/wuyuan2011woaini/article/details/9407933
這個問題曾經困擾過我一陣子。請先看一下下面的源代碼:
- class A1
- {
- public:
- int operator=(int a)
- {
- return 8;
- }
- int operator+(int a)
- {
- return 9;
- }
- };
- class B1 : public A1
- {
- public:
- int operator-(int a)
- {
- return 7;
- }
- };
- int main()
- {
- B1 v;
- cout << (v + 2) << endl; // OK, print 9
- cout << (v - 2) << endl; // OK, print 7
- cout << (v = 2) << endl; // Error, see below
- return 0;
- }
VC編譯器的錯誤提示:
error C2679: binary '=' : no operator defined which takes a right-hand operand of type 'const int' (or there is no acceptable conversion)
意思是說編譯器找不到int operator=(int a)這個成員函數可以調用。
真是怪了?明明int operator=(int a)這個函數是我從基類公有繼承下來的函數,怎么編譯器識別不了呢?遇到這種問題,第一反應就是查找MSDN以求得解釋,微軟告訴我:
“All overloaded operators except assignment (operator=) are inherited by derived classes.”
意思是說:除了賦值運算符重載函數以外,所有的運算符重載函數都可以被派生類繼承。
我這個函數int operator=(int a)很不幸就是微軟所說的“賦值運算符重載函數”,當然不能被繼承!
可是到此為止,我心中的疑問依然沒有消除。為什么“賦值運算符重載函數”不能被派生類繼承呢?從C++語義上講,不允許這個函數被派生類繼承並沒有充足的理由,一個類對象實例完全可以被任何一個其他類對象實例所賦值!比如一個顏色對象實例可以被一個整數賦值,甚至可以被一個小白兔實例所賦值。賦值運算符既然允許重載,就應該允許被繼承,就像其他的運算符重載之后都可以被派生類繼承一樣。微軟的解釋並沒有說明為什么“賦值運算符重載函數”不能被繼承的幕后原因。
我又查找了C++ Primer和其他一些重量級的C++經典,可是這些書籍對此問題都避而不談,抑或是語焉不詳,我都沒有找到答案。於是,我只好反身求諸己,從C++類對象的構造開始分析,結果找到了我認為是正確的答案:
1,每一個類對象實例在創建的時候,如果用戶沒有定義“賦值運算符重載函數”,那么,編譯器會自動生成一個隱含和默認的“賦值運算符重載函數”。所以,B1的實際上的聲明應該類似於下面這種情況:
- class A1
- {
- public:
- int operator=(int a)
- {
- return 8;
- }
- int operator+(int a)
- {
- return 9;
- }
- };
- class B1 : public A1
- {
- public:
- B1& operator =(const B1& robj); // 注意這一行是編譯器添加的
- int operator-(int a)
- {
- return 7;
- }
- };
2,C++標准規定:如果派生類中聲明的成員與基類的成員同名,那么,基類的成員會被覆蓋,哪怕基類的成員與派生類的成員的數據類型和參數個數都完全不同。顯然,B1中的賦值運算符函數名operator =和基類A1中的operator =同名,所以,A1中的賦值運算符函數int operator=(int a);被B1中的隱含的賦值運算符函數B1& operator =(const B1& robj);所覆蓋。 A1中的int operator=(int a);函數無法被B1對象訪問。
3,程序中語句v = 2實際上相當於v.operator =(2);,但是A1中的int operator=(int a);已經被覆蓋,無法訪問。而B1中默認的B1& operator =(const B1& robj);函數又與參數2的整數類型不相符,無法調用。
4,為了確認B1中默認的B1& operator =(const B1& robj);函數的存在性,可以用以下代碼驗證:
- B1 b;
- B1 v;
- v = b; // OK, 相當於調用v.operator =(b);
5,所以,“賦值運算符重載函數”不是不能被派生類繼承,而是被派生類的默認“賦值運算符重載函數”給覆蓋了。
這就是C++賦值運算符重載函數不能被派生類繼承的真實原因!
關於本帖問題正確性的解釋
C++程序員的必讀經典《Effective C++》這么說:
條款45: 弄清C++在幕后為你所寫、所調用的函數
一個空類什么時候不是空類? ---- 當C++編譯器通過它的時候。如果你沒有聲明下列函數,體貼的編譯器會聲明它自己的版本。這些函數是:一個拷貝構造函數,一個賦值運算符,一個析構函數,一對取址運算符。另外,如果你沒有聲明任何構造函數,它也將為你聲明一個缺省構造函數。所有這些函數都是公有的。換句話說,如果你這么寫:
- class Empty{};
和你這么寫是一樣的:
- class Empty {
- public:
- Empty(); // 缺省構造函數
- Empty(const Empty& rhs); // 拷貝構造函數
- ~Empty(); // 析構函數 ---- 是否
- // 為虛函數看下文說明
- Empty&
- operator=(const Empty& rhs); // 賦值運算符
- Empty* operator&(); // 取址運算符
- const Empty* operator&() const;
- };
但是Effective C++依然不能作為最后的判決。讓我們從C++的“憲法”中尋找答案...
ISO/IEC 14882是C++的國際標准。該標准於1998年9月1日通過並且定案。當然,這個標准已經不是最新標准了,但這個標准卻是目前最被廣泛支持的C++標准。所以,我一向稱之為C++的“憲法”。
C++“憲法”第12章 Special Member Functions (第185頁)開宗明義:
The default constructor, copy constructor and copy assignment operator, and destructor are special member functions. The implementation will implicitly declare these member functions for a class type when the program does not explicitly declare them, except as noted in 12.1. The implementation will implicitly define then if they are used, as specified in 12.1, 12.4 and 12.8. Programs shall not define implicitly-declared special member functions. Programs may explicitly refer to implicitly declared special member functions.
譯文:
缺省構造函數,拷貝構造函數,拷貝賦值函數,以及析構函數這四種成員函數被稱作特殊的成員函數。如果用戶程序沒有顯式地聲明這些特殊的成員函數,那么編譯器實現將隱式地聲明它們。12.1中有特別解釋的例外。如果用戶程序調用了這些特殊的成員函數,那么編譯器就會定義這些特殊的成員函數,在12.1,12.4,12.8中分別規定了編譯器對這些函數的定義方式。用戶程序不能定義隱式聲明的特殊成員函數。用戶程序可以顯式地調用隱式聲明的特殊成員函數。
譯注:
根據C++標准的規定:
聲明(Declare)代表頭文件中的那部分代碼,比如下面就是一個聲明:
- class A
- {
- public:
- A();
- }
定義(Define)代表源文件中的代碼,比如下面就是一個定義:
- A::A()
- {}
綜上所述,可知,我的第一個說法是正確的。
關於我的第二個說法的正確性,可參見C++“憲法”3.3.7 Name Hiding (第28頁)(由於我手上的C++“憲法”是掃描版,無法直接拷貝文字,且文字較多,懶得輸入了。)
我的第3,4,5點說法都是常識性的知識,可以直接驗證。