AIdrifter
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # [AIdrifter CS 浮生筆錄](https://hackmd.io/s/rypeUnYSb) <br>C++ Intrview # Smart Pointer(智能指針) > A1: 請問下面程式碼有什麼問題呢? ```C++ class object { public: int a; int b; } void Process(Object *p){ // fo somethinh with p } int main(){ while(true) { object* p = new Object(); Process(p); } } ``` Q1: 沒有人處理佔Heap空間的`object *p` -> 如果有一個幫忙管理pointer的釋放那該有多好? ```diff= - void Process(Object *p){ + void Process(SmartPOinter &p) - object* p(new Object()); + SmartPointer p(new Object); ``` :::info smart pointer其實不是一個指針,他是一個class,幫我們管理指針,維護其生命週期,再也不用擔心memory leak啦~ - How to implement smart pointer? - How to release memory?(如何) - When to release memory? (何時) ::: ## How to release memory? ### 1. 基本板 在destructor時,release memory(同歸於燼) ```C++ class SmpartPointer{ public: SmpartPointer(object *p){ ptr = p; } ~SmartPointer(){ delete ptr; } private: Object *ptr; } ``` ### 2. Reference Count 解決方方法:引用用計數! • 引用用計數:對一一個指針所指向的內存, 目前有多少個對象在使用用它 • 當引用用計數為0時,刪除對象 • 多個智能指針對象共享同一個引用用計數類 • 在進行行賦值等操作時,動態維護引用用計數 ![](https://i.imgur.com/VcLGsdn.png) 所以目前我們有兩個問題要解決 1. 如何對指針引用的內存進行計數? 2. 如何改進`SmartPointer` class使得它能動態維護引用記數? Ans: 創造一個引用計數類 ```c++ class Counter { friend class SmartPointerPro; public: Counter(){ ptr = NULL; cnt = 0; } Counter(Object *p){ ptr = p; cnt = 1; } ~Counter(){ delete ptr; } private: Object *ptr; int cnt; } ``` - 如何動態維護**引用用計數**? 引用用計數改變發生生在如下時刻: - 調用用構造函數時: ```C++ SmartPointer p(new Object()); ``` - 賦值構造函數時: ```C++ SmartPointer p(const SmartPointer &p); ``` - 賦值時: ```C++ SmartPointer p1(new Object()); SmartPointer p2 = p1 // here ``` - Final: 多考慮參數**傳遞問題** 和 **賦值問題** ```C++ class SmpartPointerPro{ public: SmpartPointerPro(object *p){ ptr_counter = new Counter(p); } SmartPointerPro(const SmartPointerPro &sp){ ptr_counter = sp.ptr_counter; ptr_counter->cnt++; } SmartPointerPro& operator (const SmartPointerPro &sp){ sp.pter_counter->cnt++; ptr_counter.cnt--; if (ptr_counter.cnt == 0) delete ptr_counter; ptr_counter = sp.ptr_counter; } ~SmartPointerPro(){ ptr_counter->cnt--; if (ptr_counter->cnt==0) delete ptr_counter; } private: Counter *ptr_counter; } ``` ## 如何獲取智能指針所包裝的指針呢? ### GetPtr() and GetObject() ```C++ class SmartPointerPro{ public: Object *Getptr(){ return ptr_counter->ptr; } Object& GetObject(){ return *(ptr_counter->ptr) } private: Counter *ptr_counter; } ``` ### Overloaded operators `->` and `*` 更自然的寫法,重載指針的操作符,使得SmartPointerPro class的對象可以像指針一樣被使用。 ```C++ class SmartPointerPro{ public: Object* operator->{ return ptr_counter->ptr; } Object& operator*(){ return *(ptr_counter->ptr) } private: Counter *ptr_counter; } int main(){ SmartPointerPro p(new Object()); p->a = 10; p->b = 20; int a_value = (*p).a; int b_value = (*p).b; } ``` ## C++ libraries 中的智能指針 ### auto_ptr C++庫中的智能指針 `std::auto_ptr`, 包含頭文文件`#include<memory>` 即可使用, 使用時注意以下問題: 1. 不是基於引用用計數的實現,一個指針在同一時刻只能被一個對象擁有 2. 儘量不要賦值,如果使用用了,請不要再使用用之前的對象 3. 不要當成參數傳遞 4. 不能放入入vector等容器中 ### boost::shared_ptr 基於reference count實做的智慧指針,需要`include <boost/smart_ptr.hpp>` - shared_ptr 看起來很完美,但是會有**循環引用**的問題: ```c++ class parent; class child; using namespace std; typedef boost::shared_ptr<parent> parent_ptr; typedef boost::shared_ptr<child> child_ptr; class parent{ public: ~parent(){ cout<<"destroying parent"<<endl;} child_ptr child; }; class child{ public: ~child() {cout<<"destroying child" << endl;} parent_ptr parent; } int main(){ parent_ptr father(new parent()); child_ptr son(new child()); // father->son->father->son.... father->children = son; son->parent = father; } ``` ### boost::weak_ptr 可以用來解決剛剛**循環引用**的問題 https://blog.csdn.net/u014294391/article/details/71435513 ### 其他問題 引申問題: java的垃圾回收機制 引用計數,分代回收,Mark and Sweep, Copy and Sweep ... https://www.cnblogs.com/dolphin0520/p/3783345.html https://www.cnblogs.com/laoyangHJ/articles/java_gc.html https://www.iteye.com/blog/jefferent-1123677 https://blog.csdn.net/initphp/article/details/30487407 # Singleton - 實例: - 螢幕上的鼠標只有一個 - 系統的日誌輸出 - 單例模式需要解決的問題: - 如何保證只產生一個object - 如何釋放該object的memory - 如何做到Thread Safe ### 1. 保證只產生一個object ```C++ #include <iostream> using namespace std; class singleton{ private: static singleton *m_instance; singleton(){}; public: static singleton *getInstance(){ if(m_instance == NULL) { m_instance = new singleton(); } return m_instance; } }; singleton *singleton::m_instance = NULL; int main(){ singleton *ptr = singleton::getInstance(); return 0; } ``` Q: 是不是還有什麼忘記考慮? Ans: 少考慮了**參數傳遞** 和 **賦值問題** ```diff= +singleton(const singleton &){} +singleton& operator=(const singleton& s){} ``` ### 2. 釋放該object的memory 如何簡單釋放最後的instacne? 機智的作法! ```diff= static singleton *getInstance(){ static singleton m_instance; return m_instance; } ``` ### 3. Thread Safe - way1: C++ 0x之後 ,剛剛的寫法就可以保證Thread Safe - way2: double check( 其實way1 就很夠了...) - check instance twice - **Lock()** and **Unlock()** ```diff class singleton{ private: static singleton *m_instance; singleton(){}; public: static singleton *getInstance(){ if(m_instance == NULL) { + Lock(); + if ( m_instance == NULL) { + Singleton* tmp = new singleton(); + m_instance = tmp; + } + Unlock(); } return m_instance; } }; ``` PS. 某些平台其實double check也是不安全的(與compiler 優化有關) http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html https://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/ # Struct 對齊問題 - struct的對齊係數和以下幾項有關 - 元素大小 - 元素順序 - `#pragma check`參數 :::info • struct中的子子項在內存中按順序排列,在沒有`#progm pack(n)`的情況,各個子項的對齊係數為自己的長度。 • 在有#progma pack(n)的情況下,各子項的對齊係數數為**min(自己的長度, n)** • struct整體的對齊係數為子項對齊係數最大的 ::: ## common > 用32bits 為access單位,擺放都是以可以一次access到對應的variable為主。 ```C++ struct A { char a; char b; char c; } ``` ![](https://i.imgur.com/XqWndHs.png) ```C++ struct A { int a; char b; short c; } ``` ![](https://i.imgur.com/qSd0uWA.png) ```C++ struct A { char b; int a; short c; } ``` ![](https://i.imgur.com/ZLQuuTU.png) ## `#prama pack` > 為了節省空間 ```C++ #define pack (2) struct A { char b; int a; short c; } ``` ![](https://i.imgur.com/yKgHdgy.png) ## 為什麼我們需要地址對齊? - 為什麼我們需要地址對齊? - 某些cpu架構不支持非對齊的地址存取 - 地址不對齊,會影響程序的效率如,如上例`int a`,會需要**兩次**地址訪問。 ![](https://i.imgur.com/yKgHdgy.png) # C++ 容易混淆的概念 ## reference vs pointer - Q:引用和指針有什麼區別? - A:本質:一個是別名,一個是地址 - 指針可以在運行時改變其所指向的值,引用一旦和某個對象綁定就不再改變 - 引用沒有const,指針有const - 從內存上看,指針會分配內存區域,而引用不會,它僅是一個別名 - 在參數傳遞時,引用用會做類型檢查,而指針不會 - 引用用不能為空,指針可以為空 ## const vs define vs inline ### const vs define - 本質:define只是字符串替換, const參與編譯運行 - define不會做類型檢查,const擁有類型,會執行相應的類型檢查 - define僅是宏替換,不佔用用內存,而而const會佔用內存 - const內存效率更高高,編譯器通常將const變量保存在符號表中,而**不會分配存儲空間**,這使得它成為一個**編譯期間的常量**,沒有**存儲**和**讀取**的操作 ### define vs inline - 本質:和前面面一樣,define只是字符串替換,inline由編譯器控制 - define只是簡單的宏替換,通常會產生二義性; 而inline會真正地編譯到代碼中 - inline函數是否展開由編譯器決定,有時候當函數太大時,編譯器可能選擇不展開相應的函數 ## malloc()/free() vs new/delete - malloc/free 是C語言的庫函數,new/delete是C++的Operator(操作符) - malloc僅用來分配內存,而不會執行相應的構造函數,函數返回值為void*,而new會調用用相應的構造函數,返回的是相應對象類型的指針 ## static ### 擴展生命週期 ex: singleton ### 限制作用域 用於全局變量,與普通全局變量不同的是。它表明該變量的作用域僅限於**當前cpp文件**。因此當其他cpp文件中同樣出現同名的static變量時,他們是不同的獨立的變量。 ### 靜態成員函數 與 靜態變量 延伸問題: - Q1:成員函數是否能同時修飾為static 和 const的呢? - A1:答案是**不可以**。 - C++編譯器在實現const的成員函數的時候為了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數`const this*`。但當一個成員為static的時候,該函數是沒有this指針的。也就是說此時const的用法和static是衝突的。 - static的作用是表示該函數只作用在class的**靜態變量**上,與類的實例沒有關係; - const的作用是確保函數**不能修改類的實例**的狀態,與類型的靜態變量沒有關係。 - Q2: static 函數能否調用**非static** 函數呢? - A2: [Callback Function](https://www.cnblogs.com/rickyk/p/4238380.html) ## const 用法 ### Base • 修飾變量 • 指向const變量的指針 && const指針 • 指向const變量指針: ```C++ const int* ptr; int const* ptr; ``` • const指針 ```C++ int* const ptr; ``` ### 真的不能修改? ```C++ #include <stdio.h> int main() { const int a = 10; int *ptr = NULL; ptr = (int*)(&a); *ptr = 20; printf("%d \n", *ptr); // 20 printf("%d \n", a); // 10 return 0; } ``` Why? Ans: Compiler會對const做優化,const變量從符號表取值。 ```shell 20 10 ``` 改成下面這樣的代碼就沒問題了 ```diff= - const int a = 10; + const volatile int a = 10; ``` ### 修飾成員函數 對於不修改成員變量的函數,一般我們都要宣稱是const函數 ```diff class point{ public: + void GetX() const { return x; } + void GetY() const { return y; } private: int x; int y } ``` ## C++的四種cast ### reinterpret_cast reinterpret_cast:**轉換一個指針為其它類型的指針**。 它也允許從一個指針轉換為整數類型,反之亦然. 這個操作符能夠在非相關的類型之間轉換. 操作結果只是簡單的從一個指針到別的指針的 值的二進制拷貝. 在類型之間指向的內容**不做任何類型的檢查和轉換**。 ```C++ class A{}; class B{}; A* a = new A; B *b = reinterpret_cast<B*>(a); ``` ### static_cast - `static_cast`允許執行任意的**隱式轉換**和相反轉換動作(即使它是不允許隱式的) - 例如:應用到類的指針上, 意思是說它允許子類型的指針轉換為父類型的指針(這是一個有效的隱式轉換), 同時, 也能夠執行相反動作: 轉換父類為它的子類: :::info 隱式轉換:編譯器根據需要自動轉換變數型別。 顯示轉換:也稱為強制型別轉換,要定義型別轉換成要用的值的型別。 ref: https://www.itread01.com/content/1547556869.html ::: ```C++ class Base {}; class Derived: public Base{}; Base *a = new Base; Derived *b = static_cast<Derived *>(a); ``` - 當然,除了指針類型,`static_cast`也可直接應用用於類,以及基礎類型之間的轉換 ### dynamic_cast `dynamic_cast`只用於對象的指針和引用. 當用於多態類型時, 它允許任意的隱式類型轉換以及相反過程. 不過,與`static_cast`不同, 在後一種情況裡(註:即隱式轉換的相反過程),dynamic_cast會檢查操作是否有效. 也就是說, 它會檢查轉換是否會返回一個被請求的有效的完整對象。檢測在運行時進行. **如果被轉換的指針不是一個被請求的有效完整的對象指針,返回值為NULL**. 對於引用類型,會拋出bad_cast異常: 在這邊,因為derived其實記憶體空間比base還要大,所以如果想要用Derived ptr去access Base Object,這邊用Dynamic cast就會被擋下來。 ```C++ class Base {virtual dummy(){}}; class Derived: public Base {}; Base *b1 = new Derived; Base *b2 = new Base; Derived d1 = dynamic_cast<Derived *>(b1); // succeeds; Derived *d2 = dynamic_cast<Derived *>(b2); // fails, return 'NULL' ``` ### const_cast 為了要移除const屬性用的: ```c++ class C {}; const C *a = new C; C *b = const_cast<C*>(a); ``` # Vritual function > Q1 : what's your output? ```C++ class Base { public: virtual void Print() const { cout<< "Print in Base" << endl; } }; class Derive: public Base { public: virtual void Print() const { cout<< "Print in Derive" << endl; } }; void Print(const Base *base) { base->Print(); } int main() { Base b; Derive d; Print(&b); Print(&d); return 0; } ``` > A1: virtual function 可以再run time實現dynamic binding ```shell Print in Base Print in Derive ``` > Q2: 如果把`virtual` 拿掉會出現什麼? ```diff -virtual void Print() const +void Print() const ``` > A2: ```shell Print in Base Print in Base ``` - 實現動態綁定有兩個條件 - 相應成員函數為virtual function - 使用base object的**reference**或**pointer**去進行調用 ## C++ virtual function底層的實現機制 - Virtual method table - virtual table pointer > 注意:虚函数表⼀一个类有⼀一个,⽽而不是⼀一个对象⼀一个 > 64bit machine have to translate `int` to `long` ![](https://i.imgur.com/PIs9sCk.png) ```C++ using namespace std; class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } private: virtual void j() { cout << "Base::j" << endl;} }; class dev: public Base { public: virtual void k() { cout << "dev::k" << endl; } }; typedef void(*Fun)(void); //void類型的函數指針 int main() { Fun pFun = (Fun)*((long*)*(long*)(&d)+0); pFun(); // f pFun = (Fun)*((long*)*(long*)(&d)+1); pFun(); // g pFun = (Fun)*((long*)*(long*)(&d)+2); pFun(); // h } ``` ```shell= Base::f Base::g Base::h ``` ### 一般繼承(無虛函數覆蓋) ![](https://i.imgur.com/s9MUuX8.png) 在這個繼承關係中,子類沒有重載任何父類的函數。那麼,在派生類的實例中,其虛函數表如下所示: ![](https://i.imgur.com/8gM0uaG.png) > 注意:⼦子类的虚函数表是⼦类的,从⽗父类拷⻉贝⼀一份过来,并进⾏行修改 ### 一般繼承(有虛函數覆蓋) ![](https://i.imgur.com/0q8PiAZ.png) 覆蓋了父類的一個函數:`f()`。那麼,對於派生類的實例,其虛函數表會是下面的一個樣子: ![](https://i.imgur.com/XYmoatZ.png) 我們從表中可以看到下面幾點, 1)覆蓋的f()函數被放到了虛表中原來父類虛函數的位置。 2)沒有被覆蓋的函數依舊。 ```C++ class dev: public Base { public: virtual void f() { cout << "dev::f" << endl; } virtual void g1() { cout << "dev::g1" << endl; } virtual void h1() { cout << "dev::h1" << endl; } }; Base *b = new dev(); b->f(); ``` ```shell= dev::f ``` ### 多重繼承(無虛函數覆蓋) ![](https://i.imgur.com/nvRpzsY.png) 我們可以看到: 1) 每個父類都有自己的虛表。 2) 子類的成員函數被放到了第一個父類的表中。(所謂的第一個父類是按照聲明順序來判斷的) > 注意:子类的虚函数`g1()`被放在了第⼀一个⽗父类中 ![](https://i.imgur.com/IIJVVWc.png) ### 多重繼承(有虛函數覆蓋) 我們在子類中覆蓋了父類的f()函數。 ![](https://i.imgur.com/Pi9JYH8.png) 我們可以看見,三個父類虛函數表中的f()的位置被替換成了子類的函數指針。這樣,我們就可以任一靜態類型的父類來指向子類 ![](https://i.imgur.com/2MEttnF.png) ```C++ class Base1 { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } }; class Base2 { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } }; class Base3 { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } }; class Derive: public Base1, public Base2, public Base3 { public: virtual void f() { cout << "Derive::f" << endl; } virtual void g1() { cout << "Derive::g1" << endl; } }; int main() { Derive d; Base1 *b1 = &d; Base2 *b2 = &d; Base3 *b3 = &d; b1->f(); //Derive::f() b2->f(); //Derive::f() b3->f(); //Derive::f() b1->g(); //Base1::g() b2->g(); //Base2::g() b3->g(); //Base3::g() return 0; } ``` ```shell Derive::f Derive::f Derive::f Base::g Base::g Base::g ``` ## 題目 ### 为什么需要虚析构函数(virtual destructor)? > 子類的constructor沒有被調用 ```C++ #include <iostream> using namespace std; class Base{ public: Base(){cout<<" Base constructor"<<endl;} ~Base(){cout<<" Base destructor"<<endl;} }; class Derive: public Base{ public: Derive(){cout<<" Derive constructor"<<endl;} ~Derive(){cout<<" Derive destructor"<<endl;} }; int main(){ Base *b = NULL; b = new Derive(); delete b; return 0; } ``` ```shell= Base constructor Derive constructor Base destructor ``` ```diff= - ~Base(){cout<<" Base destructor"<<endl;} + virual ~Base(){cout<<" Base destructor"<<endl;} ``` ```shell= Base constructor Derive constructor Derive destructor Base destructor ``` 在《Modern C++ Design》第四章有提到,在 C++ 標準中,物件的 operator delete 有兩種的重載介面: - void operator delete(void* p) - void operator delete(void* p, std::size_t size) 當你在 delete 物件時, compiler 需要知道要釋放多少的空間,而這個資訊就是需要由 size 來提供。那麼一般在重載第一種沒有提供大小資訊的 operator delete 介面時,compiler 會依照許多種作法來幫你自動產生些許的程式碼,來提供相當於 size 的資訊。在書中有提到 compiler 可能會有下列作法: 1. 由 virtual ~Derive 來提供 sizeof(Derive) 的大小。(我需要confirm這句話) 2. 將 Derive 的大小放在 vtable 裡。 以上跟書中所提到的另外兩種作法,都是在擁有 virtual 解構子的前提下。 結論就是,若是你不宣告一個 virtual 解構子,那麼 delete Derive 只會釋出 base 大小的空間,而造成一些 Undefined Behavior。不過一般良好強大的 compiler 都會偵測到這種錯誤,而噴出 warnning(而不是 error),但也是讓你編譯過,反正就是通知的義務己盡,但是運行後所產生的 UB 就要後果自負。 ### 访问虚函数和普通函数哪个快? O(1) common functino O(n) vtable, find your virtual function, dynamic binding ### 析构函数(desctructor)⼀一定是虚函数吗? 1. 訪問效率 2. 該parent class沒有子類 ### 内联函数、构造函数、静态成员函数可以是虚函数吗?” inline: 在 compiler階段就展開了,不可能dynamic binding Constructor: 父類: 父類constructor 子類: 父類constructor + 子類constructor :::info 這種性質沒有動態綁定 ::: Static 靜態成員函數: 它是一個class所有的,所有的object都是用同一個靜態成員函數。 ### 構造函數中可以調用virtual function 嗎? 可以,但是完全沒有作用。 你沒有辦法再父類剛constructor出來的 object去調用子類的方法,因為子類還沒建構出來。 https://blog.csdn.net/K346K346/article/details/49872023 ```C++ #include <iostream> using namespace std; class A { public: virtual void show(){ cout<<"show in A"<<endl; } A(){cout<<"costructor A"<<endl;} virtual ~A(){show(); cout<<"destructor A"<<endl;} }; class B:public A { public: B(){cout<<"costructor B"<<endl;} ~B(){cout<<"destructor B"<<endl;} void show(){ cout<<"show in B"<<endl; } }; int main() { cout<<" ================= "<<endl; B *b = new B(); delete b; } ``` 在類B的對象b退出作用域時,會先調用類B的析構函數,然後調用類A的析構函數,在析構函數~A()中,調用了虛函數show()。從輸出結果來看,類A的析構函數對show()的B調用並沒有發生虛調用。 ```shell= costructor A costructor B destructor B show in A # still A destructor A ``` ref: https://blog.csdn.net/K346K346/article/details/49872023 ## 虛擬繼承相關問題 這東西其實很蛋疼,老師說非常爛。 > Q1: 什麼是**虛擬繼承**,為什麼要用它? ```c++ class Base { public: void print() { cout << "Base::f" << }; class Mid1:public Base { }; class Mid2:public Base { }; class Derive:public Mid1, public Mid2 { }; int main(){ Derive d; d.print(); // compiler error } ``` A1: 虚继承⽤用来解决这种菱形继承的⼆二义性问题 虛擬繼承(使用virtual方式繼承,為了保證繼承後父類的內存佈局只會存在一份) ```diff - class Mid1:public Base + class Mid1:virtual public Base - class Mid2:public Base + class Mid2:virtual public Base ``` - How to implement: - 和编译器⾼高度相关,不同编译器实现不同 - ⼀种思路:在Mid1对象和Mid2对象中添加虚基类指针,指向基类对象(Base),这样Mid1和Mid2中就会指向共同的基类成员,从⽽而消除了⼆义性 ![](https://i.imgur.com/u56HJWI.png) Q2: 萬惡的`sizeof()`題目,此類題目與compiler高度相關,沒有唯一答案。 ```C++ #include <iostream> using namespace std; class A { public: int a; virtual void myfunA(){}; }; class B:virtual public A { public: virtual void myfunB(){}; }; class C:virtual public A { public: virtual void myfunD(){}; }; class D: public B, public C { public: virtual void myfunD(){}; }; int main(){ cout << sizeof(A) <<endl; cout << sizeof(B) <<endl; cout << sizeof(C) <<endl; cout << sizeof(D) <<endl; } ``` A2: 大小通常由這些因素決定 - 類成員的大小 - virtual table pointer大小 - 需基類指針 ```shell= 16 24 24 32 ``` # C++ 在memory中 真實的樣子 老師推薦leveldb https://github.com/google/leveldb ## 單一繼承 ![](https://i.imgur.com/37DXgPi.png) ```C++ #include <iostream> using namespace std; class Parent { public: long iparent; Parent ():iparent (10) {} virtual void f() { cout << " Parent::f()" << endl; } virtual void g() { cout << " Parent::g()" << endl; } virtual void h() { cout << " Parent::h()" << endl; } }; class Child : public Parent { public: long ichild; Child():ichild(100) {} virtual void f() { cout << "Child::f()" << endl; } virtual void g_child() { cout << "Child::g_child()" << endl; } virtual void h_child() { cout << "Child::h_child()" << endl; } }; class GrandChild : public Child{ public: long igrandchild; GrandChild():igrandchild(1000) {} virtual void f() { cout << "GrandChild::f()" << endl; } virtual void g_child() { cout << "GrandChild::g_child()" << endl; } virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; } }; int main(){ typedef void(*Fun)(void); GrandChild gc; long** pVtab = (long**)&gc; cout << "[0] GrandChild::_vptr->" << endl; for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){ Fun pFun = (Fun)pVtab[0][i]; cout << " ["<<i<<"] "; pFun(); } } ``` ```shell [0] GrandChild::_vptr-> [0] GrandChild::f() [1] Parent::g() [2] Parent::h() [3] GrandChild::g_child() [4] Child::h1() [5] GrandChild::h_grandchild() ``` ```C++ cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl; cout << "[2] Child.ichild = " << (int)pVtab[2] << endl; cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl; ``` ```shell= [1] Parent.iparent = 10 [2] Child.ichild = 100 [3] GrandChild.igrandchild = 1000 ``` > 可見以下幾個方面: 1)虛函數表在最前面的位置。 2)成員變量根據其繼承和聲明順序依次放在後面。 3)在單一的繼承中,被overwrite的虛函數在虛函數表中得到了更新。 ![](https://i.imgur.com/Ne4S2Xh.png) ## 多重繼承 假設有下面這樣一個類的繼承關係。注意:子類只overwrite了父類的`f()`函數,而還有一個是自己的函數(我們這樣做的目的是為了用`g1()`作為一個標記來標明子類的虛函數表)。而且每個類中都有一個自己的成員變量: ![](https://i.imgur.com/qpYDogO.png) ```c++ #include <cstring> #include <string.h> #include <iostream> using namespace std; class Base1 { public: long ibase1; Base1():ibase1(10) {} virtual void f() { cout << "Base1::f()" << endl; } virtual void g() { cout << "Base1::g()" << endl; } virtual void h() { cout << "Base1::h()" << endl; } }; class Base2 { public: long ibase2; Base2():ibase2(20) {} virtual void f() { cout << "Base2::f()" << endl; } virtual void g() { cout << "Base2::g()" << endl; } virtual void h() { cout << "Base2::h()" << endl; } }; class Base3 { public: long ibase3; Base3():ibase3(30) {} virtual void f() { cout << "Base3::f()" << endl; } virtual void g() { cout << "Base3::g()" << endl; } virtual void h() { cout << "Base3::h()" << endl; } }; class Derive : public Base1, public Base2, public Base3 { public: long iderive; Derive():iderive(100) {} virtual void f() { cout << "Derive::f()" << endl; } virtual void g1() { cout << "Derive::g1()" << endl; } }; int main() { typedef void(*Fun)(void); Derive d; long** pVtab = (long**)&d; cout << "[0] Base1::_vptr->" << endl; Fun pFun = (Fun)pVtab[0][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtab[0][1]; cout << " [1] ";pFun(); pFun = (Fun)pVtab[0][2]; cout << " [2] ";pFun(); pFun = (Fun)pVtab[0][3]; cout << " [3] "; pFun(); pFun = (Fun)pVtab[0][4]; cout << " [4] "; cout<<pFun<<endl; cout << "[1] Base1.ibase1 = " << (long)pVtab[1] << endl; // 對齊問題 long s = sizeof(Base1)/8; cout << "[" << s << "] Base2::_vptr->"<<endl; pFun = (Fun)pVtab[s][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtab[s][1]; cout << " [1] "; pFun(); pFun = (Fun)pVtab[s][2]; cout << " [2] "; pFun(); pFun = (Fun)pVtab[s][3]; cout << " [3] "; cout<<pFun<<endl; cout << "["<< s+1 <<"] Base2.ibase2 = " << (long)pVtab[s+1] << endl; // 對齊問題 s = s + sizeof(Base2)/8; cout << "[" << s << "] Base3::_vptr->"<<endl; pFun = (Fun)pVtab[s][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtab[s][1]; cout << " [1] "; pFun(); pFun = (Fun)pVtab[s][2]; cout << " [2] "; pFun(); pFun = (Fun)pVtab[s][3]; cout << " [3] "; cout<<pFun<<endl; s++; cout << "["<< s <<"] Base3.ibase3 = " << (long)pVtab[s] << endl; s++; cout << "["<< s <<"] Derive.iderive = " << (long)pVtab[s] << endl; return 0; } ``` ```shell [0] Base1::_vptr-> [0] Derive::f() [1] Base1::g() [2] Base1::h() [3] Derive::g1() [4] 1 [1] Base1.ibase1 = 10 [2] Base2::_vptr-> [0] Derive::f() [1] Base2::g() [2] Base2::h() [3] 1 [3] Base2.ibase2 = 20 [4] Base3::_vptr-> [0] Derive::f() [1] Base3::g() [2] Base3::h() [3] 0 [5] Base3.ibase3 = 30 [6] Derive.iderive = 100 ``` 我們可以看到: 1) 每個父類都有自己的虛表。 2) 子類的成員函數被放到了第一個父類的表中。 3) 內存佈局中,其父類佈局依次按聲明順序排列。 4) 每個父類的虛表中的f()函數都被overwrite成了子類的f()。這樣做就是為瞭解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數。 ![](https://i.imgur.com/V56LgUr.png) ## 鑽石型重複繼承 ![](https://i.imgur.com/NiamrrS.png) ![](https://i.imgur.com/VFLTVby.png) 我們可以看見,最頂端的父類B其成員變量存在於B1和B2中,並被D給繼承下去了。而在D中,其有B1和B2的實例,於是B的成員在D的實例中存在兩份,一份是B1繼承而來的,另一份是B2繼承而來的。所以,如果我們使用以下語句,則會產生二義性編譯錯誤: ```C++ D d; d.ib = 0; //二義性錯誤 d.B1::ib = 1; //正確 d.B2::ib = 2; //正確 ``` 注意,上面例程中的最後兩條語句存取的是兩個變量。雖然我們消除了二義性的編譯錯誤,但**B類在D中還是有兩個實例**,這種繼承造成了數據的重複,我們叫這種繼承為重複繼承。重複的基類數據成員可能並不是我們想要的。所以,C++引入了虛基類的概念。 ```C++ #include <cstring> #include <string.h> #include <iostream> using namespace std; class Base1 { public: long ibase1; Base1():ibase1(10) {} virtual void f() { cout << "Base1::f()" << endl; } virtual void g() { cout << "Base1::g()" << endl; } virtual void h() { cout << "Base1::h()" << endl; } }; class Base2 { public: long ibase2; Base2():ibase2(20) {} virtual void f() { cout << "Base2::f()" << endl; } virtual void g() { cout << "Base2::g()" << endl; } virtual void h() { cout << "Base2::h()" << endl; } }; class Base3 { public: long ibase3; Base3():ibase3(30) {} virtual void f() { cout << "Base3::f()" << endl; } virtual void g() { cout << "Base3::g()" << endl; } virtual void h() { cout << "Base3::h()" << endl; } }; class Derive : public Base1, public Base2, public Base3 { public: long iderive; Derive():iderive(100) {} virtual void f() { cout << "Derive::f()" << endl; } virtual void g1() { cout << "Derive::g1()" << endl; } }; int main() { typedef void(*Fun)(void); Derive d; long** pVtab = (long**)&d; cout << "[0] Base1::_vptr->" << endl; Fun pFun = (Fun)pVtab[0][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtab[0][1]; cout << " [1] ";pFun(); pFun = (Fun)pVtab[0][2]; cout << " [2] ";pFun(); pFun = (Fun)pVtab[0][3]; cout << " [3] "; pFun(); pFun = (Fun)pVtab[0][4]; cout << " [4] "; cout<<pFun<<endl; cout << "[1] Base1.ibase1 = " << (long)pVtab[1] << endl; // 對齊問題 long s = sizeof(Base1)/8; cout << "[" << s << "] Base2::_vptr->"<<endl; pFun = (Fun)pVtab[s][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtab[s][1]; cout << " [1] "; pFun(); pFun = (Fun)pVtab[s][2]; cout << " [2] "; pFun(); pFun = (Fun)pVtab[s][3]; cout << " [3] "; cout<<pFun<<endl; cout << "["<< s+1 <<"] Base2.ibase2 = " << (long)pVtab[s+1] << endl; // 對齊問題 s = s + sizeof(Base2)/8; cout << "[" << s << "] Base3::_vptr->"<<endl; pFun = (Fun)pVtab[s][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtab[s][1]; cout << " [1] "; pFun(); pFun = (Fun)pVtab[s][2]; cout << " [2] "; pFun(); pFun = (Fun)pVtab[s][3]; cout << " [3] "; cout<<pFun<<endl; s++; cout << "["<< s <<"] Base3.ibase3 = " << (long)pVtab[s] << endl; s++; cout << "["<< s <<"] Derive.iderive = " << (long)pVtab[s] << endl; return 0; } ``` To hanlde casting problem ```shell g++ -fpermissive diamod_inheritance.cpp ``` ```shell= [0] D::B1::_vptr-> [0] D::f() [1] B::Bf() [2] D::f1() [3] B1::Bf1() [4] D::f2() [5] 0x1 [1] B::ib = 0 [2] B::cb = B [3] B1::ib1 = 11 [4] B1::cb1 = 1 [5] D::B2::_vptr-> [0] D::f() [1] B::Bf() [2] D::f2() [3] B2::Bf2() [4] 0x0 [6] B::ib = 0 [7] B::cb = B [8] B2::ib2 = 12 [9] B2::cb2 = 2 [10] D::id = 100 [11] D::cd = D ``` ## 鑽石型多重虛擬繼承 虛擬繼承的出現就是為瞭解決重複繼承中多個間接父類的問題的。鑽石型的結構是其最經典的結構。也是我們在這裡要討論的結構: 上述的“重複繼承”只需要把B1和B2繼承B的語法中加上**virtual** 關鍵,就成了虛擬繼承,其繼承圖如下所示: ![](https://i.imgur.com/hD1i1C7.png) ```diff= class B {……}; - class B1 : public B{……}; - class B2 : public B{……}; + class B1 : virtual public B{……}; + class B2 : virtual public B{……}; class D : public B1, public B2{ …… }; ``` 我們先看看B1 ```c++ // 單一虛擬繼承 B1 // B1 bb1; // pVtab = (int**)&bb1 [0] B1::_vptr -> [0] : B1::f() [1] : B1::f1() [2] : B1::Bf1() [3] : 0 [1] B1::ib1 : 11 [2] B1::cb1 : 1 [3] B::_vptr -> [0] : B1::f() [1] : B::Bf() [2] : 0 [4] B::ib : 0 [5] B::cb : B [6] NULL : 0 ``` 在看看D ```c++ // 鑽石型多重虛擬繼承 D // D d; // pVtab = (int**)&d; [0] B1::_vptr -> [0] : D::f() [1] : D::f1() [2] : B1::Bf1() [3] : D::f2() [4] : D::Df() [5] : 1 [1] B1::ib1 : 11 [2] B1::cb1 : 1 [3] B2::_vptr -> [0] : D::f() [1] : D::f2() [2] : B2::Bf2() [3] : 0 [4] B2::ib2 : 12 [5] B2::cb2 : 2 [6] D::id : 100 [7] D::cd : D [8] B::_vptr -> [0] : D::f() [1] : B::Bf() [2] : 0 [9] B::ib : 0 [10] B::cb : B [11] NULL : 0 ``` ![](https://i.imgur.com/HceQIs6.png) :::info ::: # Ref: - 陳皓 C++ 虛函數表解析 https://blog.csdn.net/haoel/article/details/1948051/ C++ 对象的内存布局 https://blog.csdn.net/haoel/article/details/3081328 [C++ 對象的內存佈局(上)---陳皓改進版](https://blog.csdn.net/a3192048/article/details/82259966) [C++ 對象的內存佈局(下)---陳皓改進版](https://blog.csdn.net/a3192048/article/details/82261754) - 關於virtual 2,3事 https://medium.com/theskyisblue/c-%E4%B8%AD%E9%97%9C%E6%96%BC-virtual-%E7%9A%84%E5%85%A9%E4%B8%89%E4%BA%8B-1b4e2a2dc373 ##### tags `C++` `C++ interview` `C++ virtual function` `c++ `

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully