1.首先說一下,析構順序
派生類--> 成員類 --> 基類
2.為什么需要把基類設置為虛析構
因為多態
在c++中,可以使用父類指針指向子類,產生多態行為
代碼
#include <iostream>
class TestFather{
public:
~TestFather() {
std::cout << "~TestFather()" << std::endl;
}
};
class TestChild : public TestFather {
public:
~TestChild() {
std::cout << "~TestChild()" << std::endl;
}
};
int main() {
TestFather* p = new TestChild();
delete p;
}
//////////////////////////
執行結果
~TestFather()
原因就是,你直接給編譯器一個TestFather指針,delete的時候,編譯器一看這不就是TestFather,直接調用TestFather析構函數
換成虛析構呢
代碼
#include <iostream>
class TestFather{
public:
virtual ~TestFather() {
std::cout << "~TestFather()" << std::endl;
}
};
class TestChild : public TestFather {
public:
~TestChild() {
std::cout << "~TestChild()" << std::endl;
}
};
int main() {
TestFather* p = new TestChild();
delete p;
}
///////
執行結果
~TestChild()
~TestFather()
正確了,在delete的時候,編譯器會先看TestFather的析構函數是不是虛函數,如果是的話才會產生正常的析構順序行為,派生類-->成員類-->基類
3. 虛析構函數的本質
虛析構其實也就是虛函數加上析構函數,本質就是會維護一個虛表和指向虛表的指針,在上面代碼中TestFather這里面的虛表就只有~TestFather()這一個函數,使用虛函數代表會增加一個指針的內存開銷
4. 默認的析構函數
當我們不定義虛構函數的時候,編譯器會默認生成一個什么都不做的析構函數,但是注意了默認生成的析構函數就是普通函數不是虛函數!!!因為虛函數會帶來額外開銷,c++追求的是速度
5. 純虛構析構函數
就是純虛函數加上析構函數,一般我們把函數設置純虛函數都是不想這個類實例化,抽象出來的頂層父類,並且這個純虛函數不能實現。但是在純虛析構這里不同
代碼
#include <iostream>
class TestFather{
public:
virtual ~TestFather() = 0;
};
TestFather::~TestFather() {
std::cout << "~TestFather()" << std::endl;
}
class TestChild : public TestFather {
public:
~TestChild() {
std::cout << "~TestChild()" << std::endl;
}
};
int main() {
TestFather* p = new TestChild();
delete p;
}
//結果和上面的相同
因為析構函數的調用順序是派生類 成員類 基類,就算你基類是純虛函數,編譯器還是會產生對他的調用,所以要保證為純虛析構提供函數體,如果你不做編譯器會自動加上。
這里的純虛實現要在外面實現,不能在類中 = 0之后直接實現,那樣直接違反了語法
6. 那么如果父類有純虛析構函數,子類繼承后,怎么定義子類實例呢?
只要子類定義自己的虛函數就可以,不論繼承多少層,只要有一個子類定義自己的虛函數,這個子類之后的派生類就都可以定義實例了
7. 關於virtual的隱士傳播
在上面的代碼中我們基類設置為純虛函數的時候,這個virtual關鍵字會被一直繼承下去
代碼
#include <iostream>
class TestFather{
public:
virtual ~TestFather() = 0;
};
TestFather :: ~TestFather() {
std::cout << "~TestFather()" << std::endl;
}
class TestChild : public TestFather {
public:
~TestChild() {
std::cout << "~TestChild()" << std::endl;
}
};
class TestSun : public TestChild {
public:
~TestSun() {
std::cout << "~TestSun()" << std::endl;
}
};
int main() {
TestFather* p = new TestSun();
delete p;
}
結果:
~TestSun()
~TestChild()
~TestFather()
所以當基類是virtual函數,無論子類的相同函數加或者不加這個關鍵字都是virtual的,但是為了其他人看代碼方便,建議手動把從基類繼承的虛函數加上virtual
7. 總結:
明確你的類會不會被繼承,當作基類使用,把類的析構函數都設置為虛函數和不設置為虛函數都是不好的
1.如果你的類會被繼承,當作基類,那么一定要把基類析構函數設置為虛函數
2.如果你的類不會被繼承,單純的類,那么不需要把析構函數設置為析構函數,因為會浪費空間,多一個虛表指針