# [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++ `