Data Member 的存取
考察以下代碼:
Point3d origin; origin.x = 0.0;此例中 x 的存取成本是什么? 答案則是視 x 和 Point3d 而定(別打臉, 我知道這是廢話)。 具體的呢? 因為 x 可能是個 static member, 也可能是個 nonstiatic member; Point3d 可能是個獨立的 class, 也可能是另一個 單一的class 派生而來;甚至可能是從多重繼承或虛擬繼承而來(請不要小看其他人的代碼中的可能性, 你都很有可能不知道 C++ 還能這么寫, 有時在這句話之后還能加一個“我勒個去”) 而下面, 咱們就來檢驗這每一種可能性。
在開始之前, 先拋出一個問題, 如果我們有兩個定義, origin 和 pt:
Point3d origin, *pt = &origin; //用他們來存取 data members, 像這樣: origin.x = 0.0; pt->x = 0.0;通過 origin 存取和 pt 存取, 有什么重大差異呢? 讓我們在最后得出答案。
Static Data Members
static data members 被編譯器提出于 class 之外, 并被視為一個 global 變量(但只在 class 生命范圍內可見)。 每一個 member 的存取許可(無論是 public, private 還是 protected), 以及與 class 的關聯, 都不會導致時間或是空間上的額外負擔(無論是個別的 class object 還是 static data member 本身), 因為每一個 static data member 都只有一個實體, 存放在程序的 data segment 之中, 每次取用 static member, 就會被內部轉化為唯一的 extern 實體的直接參考操作, 如:
從指令執行的觀點來看, 這是 C++ 語言中通過一個指針和通過一個對象來存取 member, 結論完全相同的唯一一種情況。 這是因為經由 member selection operators(說人話就是 '.' 運算符) 對一個 static data member 進行存取操作只是語法上的一種變異形式而已。 member 其實并不在 class object 中, 因此 存取 static members 并不需要通過 class object。
那么如果 chunkSize 是一個從復雜關系中繼承而來的 member, 又當如何? 或許它是一個 virtual base class 的 virtual base class(或更加復雜的情況) 的 member 也說不定, 那咋辦呢?哦, 無所謂, 程序之中對于 static members 還是只有唯一一個實體, 而其存取路徑依然是那么直接。
那么如果 static data member 的存取是經由函數調用(或其他某些語法) 而被存取呢? 例如:
調用 Foobar() 會發生什么事? 在 C++ 的標準中, 沒人知道會發生什么事,因為 ARM 并未指定 Foobar() 是否必須被求值(evaluated)。 cfront 的做法就是把它丟掉(= =!) 但 C++ Standard 明確要求 Foobar() 必須被求值, 哪怕其結果毫無用處, 下面就是一種可能的轉化:
//你看到的 //Foobar().chunkSize = 250; //實際上可能的代碼 //對表達式求之后, 丟棄結果 (void) Foobar(); Point3d.chunkSize = 250;若取一個 static data member 的地址, 會得到一個指向其數據類型的指針, 而不是一個指向其 class member 的指針, 因為 static member 并不在內含在一個 class object 之中,例如:
&Pointd::chunkSize; //會得到如下的內存地址: const int*那如果有兩個 classes, 每一個都聲明了一個 static member freeList, 那么當他們都被放在程序的 data segment 時, 就會導致名稱沖突, 對此編譯器的解決方案是暗中對每一個 static data member 編碼(這個手法有一個很美的名稱: name-mangling) 以獲得一個獨一無二的程序識別代碼, 有多少編譯器就有多少種 name-mangling 做法。所謂的 name-mangling 做法主要就是兩點:
1. 一種算法, 推導出獨一無二的名稱;
2. 萬一編譯系統必須要和使用者交談,那些獨一無二的名稱可以輕易被推導回到原來的名稱。
Nonstatic Data Members
Nonstatic data members 直接存放在每一個 class object 之中。 除非經由明確的(explicit) 或暗喻的(implicit) class object, 不然沒有辦法存取它們。只要程序員在一個 member function 中直接處理一個 nonstatic data member, 所謂 implicit class object 就會發生, 考察以下代碼:
欲對一個 nonstatic data member 進行存取操作, 編譯器需要把 class object 的起始地址加上 data member 的偏移量, 例如:
origin._y = 0.0;那么地址 &origin + (&Point3d::_y - 1);
要注意的是其中的 -1 操作, 指向 data member 的指針, 其偏移量(offset) 的值總是被加上 1, 這樣可以使編譯系統區分出 “一個指向 data member 的指針, 用以指出 class 的第一個 member” 和 “一個指向 data member 的指針, 沒有指出任何member” 兩種情況。其中指向 data members 的指針將在以后的博客中探討。
每一個 nonstatic data member 的偏移量在編譯時起即可獲知, 甚至如果 member 屬于一個 base class subobject(派生自單一或多重繼承串鏈) 也是一樣, 因此存取一個 nonstatic data member 的效率 == C struct member == nonderived class 的 member 。
但是對于虛擬繼承略有不同, 虛擬繼承將為經由 base class subobject 存取 class members 導入一層新的間接性, 例如:
其執行效率在 _x 是一個 stuct member, 一個 class member, 單一繼承, 多重繼承的情況下都完全相同, 但如果 _x 是一個virtual base class 的 member, 存取速度會慢一點。
回到一開始的問題, 從 origin 存取和從 pt 存取 有什么重大差異? 答案是當 Point3d 是一個derived class, 而在其繼承結構中有一個 virtual base class, 且被存取的 member 是一個從該 virtual base class 繼承來的 member 時, 就會產生重大差異。 因為這個時候我們無法確定 pt 到底指向哪一種 class type(這就導致我們無法知道編譯期這個 member 真正的 offset 位置), 所以這個存取必須延遲到執行期, 經由一個額外的間接導引, 才能解決。 但如果用 origin 就不存在這樣的問題,因為 origin 的歸屬毫無疑問, 而他繼承自 virtual base class, member 的 offset 位置也在編譯時期就固定了, 一個積極進取的編譯器甚至可以靜態的經由 origin 就解決掉對 _x 的存取。
?以上。
轉載于:https://www.cnblogs.com/wuOverflow/p/4108128.html
總結
以上是生活随笔為你收集整理的Data Member 的存取的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是关键字驱动框架(自动化测试)
- 下一篇: 11月19日随笔