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)