由创建一个不能被继承的类引发的对象模型的思考
昨天吃飯和寬妹討論起以前看過的如何創建一個不能被繼承的類,具體實現見
http://blog.csdn.net/kuaile123/article/details/21321471
同學提到了第一種方法,說new過之后還需要delete釋放內存,這樣需要把析構函數也設置為private。
然后就想到能不能創建一個變量,返回此變量,而不是new一個,這樣可以將析構函數設置為pubic不需要delete。寬妹說這種方法肯定行不通,因為這樣創建是在棧上創建的,當返回時變量已經無效了。事實證明是可行的。
class A{ private:int i;A( int j=0 ){ i=j; printf("construct: %d \n",i);} public:static A get_A0(){A a(2);return a;} }; A a0 = A::get_A0();//調用后討論說他說的是返回變量的地址是有錯的,上述之所以成功是因為后面調用后使用了一個復制構造函數,這樣會造成效率低。然后再次寫了返回變量地址的方法,如下所示:
//class A 定義里面 public:static A* get_A1(){A a(2); return &a; }A* a1 = A::get_A1();//調用顯示創建是沒用問題的,寬妹說調用此對象的一個成員函數肯定會出錯,由此,我們添加了一個成員函數,如下所示:
//class A 定義里面 public : int f(){printf("%d \n",i);return i;} //調用 a1->f();輸出為:
construct: 2
4062396
而new的調用是這樣的:
static A* get_A2(){ return new A(3); } A* a2 = A::get_A2(); a2->f();輸出為:
construct: 3
3
顯示堆上創建的調用是沒有錯的,棧上創建的調用結果是錯誤的。因為棧上創建的是局部變量,用new是在堆上創建的,如果不手動釋放會一直存在,而在棧上創建的出了作用域會失效,所以在堆上創建調用函數時沒有問題的。
但是依然可以打印說明仍然可以進入函數,但是只是打印成員數據時出錯,此時我們討論了C++對象模型,寬妹認為成員函數是不放置在類對象里的,而是經過混淆放置于一個全局的位置,所以雖然類對象是錯誤的,但是依然可以調用成員函數。為此又寫了一個調用方法來證實,將隨便一個變量的地址賦值給類對象,照樣能打印,數據還是此變量的值,如下所示:
int i =100;A* a = reinterpret_cast<A*>(&i);a->f();輸出為:
100
由此可見沒有調用構造函數直接訪問了i的地址。我們翻了《C++對象模型》這本書,討論了出現這種結果的原因,C++對象模型中,只有非靜態數據成員被放置在每一個類對象中,而其他的如靜態數據成員、靜態和非靜態函數成員都是放置于類對象外,可以理解為一個類的空間中,每個對象只是共享同一份。這樣也就解釋了為什么類對象錯誤但是依然能夠調用類成員的原因。當創建一個類變量返回該變量的地址時,復制該指針,而當退出此函數時隨即此變量已經失效,a1指針實際上指向的是一塊無效的棧空間,當調用函數f()時,只是從類中調用,不經過類對象a1,f()中需要對象的數據成員i,就到認為正確的地址空間去找,a1+數據成員在a1中的偏移,而此時此段可能已經被改寫,此時不會返回正確的結果。同理,我們隨便將一個變量的地址強制轉換為類對象的指針,此時也是上述的流程,上述巧合是因為第一個聲明的成員變量地址和類對象地址是一致的,而且對象中的i和所聲明變量都是int型,讀取時都會讀取i的地址,當我們將變量設置為float等,此時就會有截斷等,打印出的也不是變量的值。
有些問題在書上看到過,但是距離用理論來分析還是有一定距離,幸而周圍有幾個小伙伴,尤其是寬妹,讓我對一些東西的理解更深一些。三個臭皮匠頂個諸葛亮,O(∩_∩)O哈哈~
總結
以上是生活随笔為你收集整理的由创建一个不能被继承的类引发的对象模型的思考的全部內容,希望文章能夠幫你解決所遇到的問題。