error2248 operator = 的錯誤

轉載自:http://blog.csdn.net/vincent_lon/article/details/2950218


1>e:/program files/microsoft visual studio 9.0/vc/atlmfc/include/afxtempl.h(776) : error C2248: 'CObject::operator =' : cannot access private member declared in class 'CObject'
1>        e:/program files/microsoft visual studio 9.0/vc/atlmfc/include/afx.h(562) : see declaration of 'CObject::operator ='
1>        e:/program files/microsoft visual studio 9.0/vc/atlmfc/include/afx.h(532) : see declaration of 'CObject'
1>        This diagnostic occurred in the compiler generated function 'CList<TYPE,ARG_TYPE> &CList<TYPE,ARG_TYPE>::operator =(const CList<TYPE,ARG_TYPE> &)'
1>        with
1>        [
1>            TYPE=CProgram,
1>            ARG_TYPE=CProgram &
1>        ]

上面這段編譯器報警是不是有似曾相識的感覺?想必很多人在用VC2005以及之後的版本的VC編譯器時看到過這個東西,在google查一下error C2248: 'CObject::operator =' : cannot access private member declared in class 'CObject' 你會發現很多人碰到類似的問題。一位叫“中國民工”的blog中說明了引發這一問題的的原因,請參見http://www.cppblog.com/hlong/archive/2007/11/20/37015.html

根據民工兄的解釋,由於我們的類中定義了CArray類型的成員,而CArray<A, A&>類的operator=是private類型(它繼承自CObject::operator=,且被定義爲private類型)。並給出瞭解決之道:如果我們的類/結構體中有CArray(或CList等其他的派生自CObject類)的成員變量,我們最好添加上一個public類型的operator=運算賦重載函數;

問題雖然是解決了,但是似乎還是沒弄明白產生問題的根本原因:既然問題是由於CArray沒有定義Operator=操作從而導致需要調用基類CObject中的私有Operator=操作而引起的,那麼爲什麼微軟要將CObject的Operator=操作定義成私有呢?或者說爲什麼CObject在實際上並沒有對Operator=和拷貝構造函數做任何實質性的定義的情況下要去定義這兩個函數,並且還將他們定義爲private。。唯一的解釋就是微軟不希望繼承自CObject的類使用默認的Operator=操作,或者說微軟不希望使用了CArray或者CList或者CMap的Collections模板類的類使用默認的Operator=操作。答案的確是如此,不僅如此,對於默認的拷貝構造函數結論也是一樣(可能有些人碰到到同樣的編譯器報錯,不過是針對拷貝構造函數的,後面我會解釋引發這兩種類型的報錯的真正原因)。

要想搞清楚這個問題,就先要了解C++編譯器生成的默認函數——這些我們平時一直在使用卻很少關心的幕後工作者到底是怎樣工作的。當我們創建一個C++變量,爲一個變量賦值,或者調用一個C++函數,或者對一個C++變量進行取地址&操作時,這些都是一些基本操作,我們總是能得到我們想要的結果(至少看起來是這樣)因爲這些都是C++的語義,而負責實現這些語義的是C++編譯器。

編譯器又是如何實現這些語義的呢,比如當你構造一個C++變量時,編譯器會檢查你有沒有爲這個變量的類型創建一個構造函數,如果沒有,編譯器會自己幫你生成一個默認的構造函數,從而實現了一個C++變量的構造;類似的,在用戶執行其他一些諸如賦值操作,拷貝構造,取地址等基本操作時,編譯器遵循同樣的原則,即在用戶未定義相應的操作情況下自動生成一個默認的操作;正因爲如此,甚至很多程序員並不知道它們的存在(我記得幾年前我去一家公司面試的時候,就被問及過這樣一個問題:C++類的默認函數有幾個,分別是什麼,,其實這是一個蠻深刻的問題),即使知道它們,也會覺得在不需要我們付出任何關心的情況下,這些操作都是理所應當應該是正確的,但是事實並非總是如此。

 

Effective C++在條款45也對這幾個函數進行了比較詳細的分析;其中構造函數和析構函數其實就是一個空函數,什麼也不做,所以當我們用VC也好或者其他的C++編譯器創建一個空類也好,編譯器都會爲我們的類事先定義一個構造和析構函數,就是爲了不讓我們去使用默認的構造和析構函數。所以一般情況下我們很少會忽視這兩個函數的存在,但是另外3個很重要的函數:拷貝構造和賦值操作符,以及取地址操作符就很容易被忽視掉,因爲我們很少去實現這3個操作,大部分都是使用的編譯器提供的默認的實現。

對於默認賦值操作符和拷貝構造函數的默認實現,Effective C++上的解釋是:官方的規則是:缺省拷貝構造函數(賦值運算符)對類的非靜態數據成員進行 "以成員爲單位的" 逐一拷貝構造(賦值)。即,如果m是類C中類型爲T的非靜態數據成員,並且C沒有聲明拷貝構造函數(賦值運算符),m將會通過類型T的拷貝構造函數(賦值運算符)被拷貝構造(賦值)---- 如果T有拷貝構造函數(賦值運算符)的話。如果沒有,規則遞歸應用到m的數據成員,直至找到一個拷貝構造函數(賦值運算符)或固定類型(例如,int,double,指針,等)爲止。默認情況下,固定類型的對象拷貝構造(賦值)時是從源對象到目標對象的 "逐位" 拷貝。對於從別的類繼承而來的類來說,這條規則適用於繼承層次結構中的每一層,所以,用戶自定義的構造函數和賦值運算符無論在哪一層被聲明,都會被調用。

另外一個很重要的概念是:當且僅當相應的操作被定義時,編譯器才爲操作變量所屬的類型生成對應的默認操作;也就是說,如果代碼中沒有出現相應的操作,編譯器什麼也不會做。

再回到我們一開始民工兄的例子中,假設我們的類C中定義了一個CArray類型的成員變量,並且C中沒有顯示的定義operator=操作(這就意味着編譯器可能需要爲C自動生成一個默認的operator=操作)根據上面的說法,當且僅當C的實例被賦值時編譯器纔會生成默認的operator=操作,而編譯器報警告訴我們,編譯器在生成operator=操作時遇到了障礙,這個障礙就是:C的CArray成員具有一個賦值運算符定義,但是該定義是私有的(實際上是CArray繼承自CObject的operator=定義)。。我們看一下民工兄的例子的源碼:

 3 struct A
 4 {
 5     int a;
 6 };
 7 
 8 struct B
 9 {
10     CArray<A, A&> b;
24 };
25 
26 typedef CArray<B, B&> C;
27 
28 void test()
29 {
30     B b;
31     C c;
32     c.Add(b); 
33 }

從這段代碼中可以看出,test中對B的實例b並沒有做任何賦值的操作,爲什麼編譯器會去實現B的默認operator=操作呢?

問題就處在CArray::Add()的函數簽名身上,Add函數的源碼如下

template<class TYPE, class ARG_TYPE>
AFX_INLINE INT_PTR CArray<TYPE, ARG_TYPE>::Add(ARG_TYPE newElement)
 { INT_PTR nIndex = m_nSize;
  SetAtGrow(nIndex, newElement);

  return nIndex; }

可見,形參newElement的類型是ARG_TYPE,ARG_TYPE其實就是TYPE的引用,在這裏就是B的引用;答案似乎明瞭了:

因爲Add函數的簽名要求輸入參數的類型爲B&,所以當編譯器編譯語句:c.Add(b); 時會自動將b強制轉換成B的引用,即將這條語句編譯成:B& d = b; c.Add(d); 賦值操作就這樣出現了!

爲了驗證這個想法,可以做另一個測試,調用一個傳值的函數,這樣就不需要做“B& d = b;”的轉換了,那麼就應該不會有問題了。

很不幸,實際上編譯器會報同樣的錯誤,只是現在不是operator=而是拷貝構造函數。

原因很簡單,因爲對於傳值的方式,實際上就是“通過值來傳遞一個對象”,而這又是由這個對象的類的拷貝構造函數定義的。也就是說當通過傳值的方式調用一個子程序時,最終是編譯器通過調用拷貝構造函數來實現的。所以問題是一樣的,因爲類型B沒有定義自己的拷貝構造函數,於是編譯器試圖生成一個默認的,但它同樣遇到了障礙,因爲CArray的拷貝構造也是private的(同樣繼承於CObject)。

至此,我們對於這個問題有了一個比較全面的認識,其實問題的本質在於:不論是默認的operator=還是默認的拷貝構造也好都是有其致命的缺陷的,正如Effective C++條款11中所描述的,當這些默認的實現在遇到需要動態分配內存的類的時候就會出現問題,也就是所謂的深拷貝問題。而Collections模板類就是典型的不能使用默認operator=和默認的拷貝構造的例子。

所以當我們自定義的類中包含Collections模板類的成員時我們就沒法再偷懶使用編譯器自動生成的operator=和的拷貝構造了,而必須自己動手實現,這也是微軟想提醒我們的一個事實。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章