1虛函數實現原理: base class 包括1,2,3步驟,derive class 包括1,3 兩個步驟。node
編譯器作的三項工做ios
1) 爲每個有虛函數的類在static memory 中建vtable []= {fun_ptr1,fun_ptr2,......}c++
2)爲每個有虛函數的基類vtable 中的函數指針指向基類的虛函數。(子類繼承時cp vtable 中的函數指針,xcode
並更新子類覆蓋的虛函數給相應的指針)app
3)爲每個有虛函數的類對象增長影藏字段vtable* vt_ptr在調用構造函數的時候生成更新vt_ptr = &vtable[0] 代碼ide
https://isocpp.org/wiki/faq/virtual-functions函數
What happens in the hardware when I call a virtual function? How many layers of indirection are there? How much overhead is there? This is a drill-down of the previous FAQ. The answer is entirely compiler-dependent, so your mileage may vary, but most C++ compilers use a scheme similar to the one presented here. Let’s work an example. Suppose class Base has 5 virtual functions: virt0() through virt4(). // Your original C++ source code class Base { public: virtual arbitrary_return_type virt0( /*...arbitrary params...*/ ); virtual arbitrary_return_type virt1( /*...arbitrary params...*/ ); virtual arbitrary_return_type virt2( /*...arbitrary params...*/ ); virtual arbitrary_return_type virt3( /*...arbitrary params...*/ ); virtual arbitrary_return_type virt4( /*...arbitrary params...*/ ); // ... }; Step #1: the compiler builds a static table containing 5 function-pointers, burying that table into static memory somewhere. Many (not all) compilers define this table while compiling the .cpp that defines Base’s first non-inline virtual function. We call that table the v-table; let’s pretend its technical name is Base::__vtable. If a function pointer fits into one machine word on the target hardware platform, Base::__vtable will end up consuming 5 hidden words of memory. Not 5 per instance, not 5 per function; just 5. It might look something like the following pseudo-code: // Pseudo-code (not C++, not C) for a static table defined within file Base.cpp // Pretend FunctionPtr is a generic pointer to a generic member function // (Remember: this is pseudo-code, not C++ code) FunctionPtr Base::__vtable[5] = { &Base::virt0, &Base::virt1, &Base::virt2, &Base::virt3, &Base::virt4 }; Step #2: the compiler adds a hidden pointer (typically also a machine-word) to each object of class Base. This is called the v-pointer. Think of this hidden pointer as a hidden data member, as if the compiler rewrites your class to something like this: // Your original C++ source code class Base { public: // ... FunctionPtr* __vptr; // Supplied by the compiler, hidden from the programmer // ... }; Step #3: the compiler initializes this->__vptr within each constructor. The idea is to cause each object’s v-pointer to point at its class’s v-table, as if it adds the following instruction in each constructor’s init-list: Base::Base( /*...arbitrary params...*/ ) : __vptr(&Base::__vtable[0]) // Supplied by the compiler, hidden from the programmer // ... { // ... } Now let’s work out a derived class. Suppose your C++ code defines class Der that inherits from class Base. The compiler repeats steps #1 and #3 (but not #2). In step #1, the compiler creates a hidden v-table, keeping the same function-pointers as in Base::__vtable but replacing those slots that correspond to overrides. For instance, if Der overrides virt0() through virt2() and inherits the others as-is, Der’s v-table might look something like this (pretend Der doesn’t add any new virtuals): // Pseudo-code (not C++, not C) for a static table defined within file Der.cpp // Pretend FunctionPtr is a generic pointer to a generic member function // (Remember: this is pseudo-code, not C++ code) FunctionPtr Der::__vtable[5] = { &Der::virt0, &Der::virt1, &Der::virt2, &Base::virt3, &Base::virt4 ↑↑↑↑ ↑↑↑↑ // Inherited as-is }; In step #3, the compiler adds a similar pointer-assignment at the beginning of each of Der’s constructors. The idea is to change each Der object’s v-pointer so it points at its class’s v-table. (This is not a second v-pointer; it’s the same v-pointer that was defined in the base class, Base; remember, the compiler does not repeat step #2 in class Der.) Finally, let’s see how the compiler implements a call to a virtual function. Your code might look like this: // Your original C++ code void mycode(Base* p) { p->virt3(); } The compiler has no idea whether this is going to call Base::virt3() or Der::virt3() or perhaps the virt3() method of another derived class that doesn’t even exist yet. It only knows for sure that you are calling virt3() which happens to be the function in slot #3 of the v-table. It rewrites that call into something like this: // Pseudo-code that the compiler generates from your C++ void mycode(Base* p) { p->__vptr[3](p); } On typical hardware, the machine-code is two ‘load’s plus a call: The first load gets the v-pointer, storing it into a register, say r1. The second load gets the word at r1 + 3*4 (pretend function-pointers are 4-bytes long, so r1 + 12 is the pointer to the right class’s virt3() function). Pretend it puts that word into register r2 (or r1 for that matter). The third instruction calls the code at location r2. Conclusions: Objects of classes with virtual functions have only a small space-overhead compared to those that don’t have virtual functions. Calling a virtual function is fast — almost as fast as calling a non-virtual function. You don’t get any additional per-call overhead no matter how deep the inheritance gets. You could have 10 levels of inheritance, but there is no 「chaining」 — it’s always the same — fetch, fetch, call. Caveat: I’ve intentionally ignored multiple inheritance, virtual inheritance and RTTI. Depending on the compiler, these can make things a little more complicated. If you want to know about these things, DO NOT EMAIL ME, but instead ask comp.lang.c++.
2static init order: C++ 不保證靜態成員初始化順序的嚴格性,因此發明了局部靜態變量填坑,具體看標準。測試
3static init thread_safe:IOS xcode 坑,編譯選項中static init thread_safe 默認關閉。fetch
4純虛函數的做用就是標記這個類爲抽象類,不能實例化。之因此不相似JAVA 在類級別加abstract 關鍵字解決;是設計理念使然,官方提供的解釋就是方法級更靈活。純虛方法也能夠有方法定義體,只是系統不會自動調用,人爲調用也能夠(純虛方法設計場景,就是不想讓你調用基類的純虛方法)。因此虛方法和純虛方法的惟一區別就是純虛方法標記類是抽象類不能實例化。ui
5類中有純虛函數,必須實現虛析構方法,不解釋。
6構造函數不能是虛函數,虛函數與構造函數在類型肯定性上概念互斥。析構函數若是須要經過 delete Base* 來乾淨的釋放Base* 指向的子類對象必須定義爲虛函數。
7在子類構造方法中不要調用虛方法,由於調用子類構造方法時,子類尚未徹底構造完成。僅僅是父類構造完畢。你調用子類的虛方法可能只能調用到父類的虛方法與你的想法相悖。(析構方法中也不要調用虛函數)
8, 子類中的構造方法調用是:先構造父類若是子類構造中沒有顯式指定調用父類哪個構造方法,則調用父類默認無參構造,而後在調用子類構造;注意子類只能調用直接基類構造方法。
9,全局變量,全局靜態變量,局部靜態變量的構造,析構順序:
全局變量,全局靜態變量構造順序是隨機的C++ 沒有規定和編譯器相關,C++只規定析構和構造順序相反。
構造:
1,全局靜態變量,全局變量: static Global_init
2,.init section : __attribute__((constructor)) static void liyl_libc_preinit() ;
3,main 方法開始:void SetUp();
18 class HttpDownloaderTest: public ::testing::Test { 19 public: 20 virtual void SetUp(){ 21 ENTER_FUNC; 22 mManager = LL_LoadManager::getInstance().get(); 23 node_id node = -1; 24 } 25 virtual void TearDown(){ 26 ESC_FUNC; 27 } 28 29 public: 30 LL_LoadManager* mManager; 31 };
4,局部靜態變量,在調用LL_LoadManager::getInstance().get()初始化。
45 /*static*/ 46 sp<LL_LoadManager>& LL_LoadManager::getInstance() { 47 Mutex::Autolock lock(sLock); 48 //C++ standard : don't delete local static 49 static sp<LL_LoadManager> sInstance = new LL_LoadManager(); 50 return sInstance; 51 } 52
析構:
1,main() 方法結束:void TearDown();
2,局部靜態變量:static sp<LL_LoadManager> sInstance 析構
3, .fini section :int ret_value = __cxa_atexit(malloc_fini_impl, nullptr, nullptr);
4,全局變量,全局靜態變量:static Global_init
10. 空指針調用類方法:1,平凡c++類的空指針對象調用普通成員函數(函數中沒有用到成員變量)正常調用 2,c++抽象類的空指針對象調用普通成員函數(函數中沒有用到成員變量)正常調用 3,c++抽象類的空指針對象調用成員虛函數(函數中沒有用到成員變量)crash
7 #ifndef TMP_H 8 #define TMP_H 9 #include <iostream> 10 11 using std::cout; 12 using std::endl; 13 class A { 14 public : 15 void myprint() { 16 cout << "A.a = 1"<<endl; 17 } 18 private: 19 int a; 20 }; 21 22 class B { 23 public : 24 virtual void myprint() { 25 cout << "B.a = 2"<<endl; 26 } 27 void myprint1() { 28 cout << "B.a = 3"<<endl; 29 } 30 private: 31 int a; 32 }; 33 #endif //TMP_H 159 TEST_F(Test, test2) { // test 160 A* aaa = new A(); 161 B* bbb = new B(); 162 aaa->myprint(); 163 bbb->myprint(); 164 165 delete aaa; 166 aaa = NULL; 167 aaa->myprint(); 168 delete bbb; 169 bbb = NULL; 170 bbb->myprint1(); 171 bbb->myprint(); 172 }
測試結果:
[==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from Test [ RUN ] Test.test2 A.a = 1 B.a = 2 A.a = 1 B.a = 3 Segmentation fault (core dumped)