1. 程式人生 > >[收集]c++抽象類、純虛擬函式以及巧用純虛解構函式實現介面類

[收集]c++抽象類、純虛擬函式以及巧用純虛解構函式實現介面類

在Java、C#中有關鍵詞abstract指明抽象函式、抽象類,但是在C++中沒有這個關鍵詞,很顯然,在C++也會需要只需要在基類宣告某函式的情況,而不需要寫具體的實現,那C++中是如何實現這一功能的,答案是純虛擬函式 含有純虛擬函式的類是抽象類,不能生成物件,只能派生。他派生的類的純虛擬函式沒有被改寫,那麼它的派生類還是個抽象類。定義純虛擬函式就是為了讓基類不可例項化化意義。

一.  純虛擬函式

在許多情況下,在基類中不能給出有意義的虛擬函式定義,這時可以把它說明成純虛擬函式,把它的定義留給派生類來做。定義純虛擬函式的一般形式為:

class 類名{

 virtual 返回值型別 函式名(引數表)= 0;  // 後面的"= 0"是必須的,否則,就成虛函數了

};

純虛擬函式是一個在基類中說明的虛擬函式,它在基類中沒有定義,要求任何派生類都定義自己的版本。純虛擬函式為各派生類提供一個公共介面。

從基類繼承來的純虛擬函式,在派生類中仍是虛擬函式。

二. 抽象類

1. 如果一個類中至少有一個純虛擬函式,那麼這個類被稱為抽象類(abstract class)。

抽象類中不僅包括純虛擬函式,也可包括虛擬函式。抽象類中的純虛擬函式可能是在抽象類中定義的,也可能是從它的抽象基類中繼承下來且重定義的。

2. 抽象類特點,即抽象類必須用作派生其他類的基類,而不能用於直接建立物件例項。

一個抽象類不可以用來建立物件,只能用來為派生類提供一個介面規範,派生類中必須過載基類中的純虛擬函式,否則它仍將被看作一個抽象類。

3. 在effective c++上中提到,純虛擬函式可以被實現(定義)(既然是純虛擬函式,為什麼還可以被實現呢?這樣做有什麼好處呢?下文中“巧用純虛解構函式實現介面類”中將說明這一功能的目的。),但是,不能建立物件例項,這也體現了抽象類的概念。

三. 虛解構函式

虛解構函式: 在解構函式前面加上關鍵字virtual進行說明,稱該解構函式為虛解構函式。雖然建構函式不能被宣告為虛擬函式,但解構函式可以被宣告為虛擬函式

一般來說,如果一個類中定義了虛擬函式, 解構函式也應該定義為虛解構函式。

例如:

class B

{

 virtual ~B();  //虛解構函式

  …

};

下面介紹一些例項:

#include <stdio.h>

class Animal
{
public:
     Animal()	//建構函式不能被宣告為虛擬函式
     {
	printf(" Animal construct! \n");
     }
     virtual void shout() = 0;
     virtual void impl() = 0;
     virtual ~Animal() {printf(" Animal destory! \n");};   // 虛解構函式
};


void Animal::impl()        // 純虛擬函式也可以被實現。
{
     printf(" Animal: I can be implement! \n");
}


class Dog: public Animal
{
public:
     Dog()
     {
	printf(" Dog construct! \n");
     }
     virtual void shout() // 必須要被實現,即使函式體是空的
     {
		 printf(" Dog: wang! wang! wang! \n");
     }
     virtual void impl()
     {
		 printf(" Dog: implement of Dog!  \n");
     }
     virtual ~Dog() {printf(" Dog destory! \n");};   // 虛解構函式
};


class Cat: public Animal
{
public:
     Cat()
     {
	printf(" Cat construct! \n");
     }
     virtual void shout() // 必須要被實現,即使函式體是空的
     {
		 printf(" Cat: miao! miao! miao! \n");
     }
    
     virtual void impl()
     {
		 printf(" Cat: implement of Cat!  \n");
     }
     virtual ~Cat() {printf(" Cat destory! \n");};   // 虛解構函式
};


/*
Animal f()  // error, 抽象類不能作為返回型別
{
      
}

void display( Animal a) //error, 抽象類不能作為引數型別
{
    
}
*/


//ok,可以宣告抽象類的引用
Animal &display(Animal &a)
{
       Dog d;
       Animal &p = d;
       return p;
      
}

void test_func()
{
     //Animal a;  // error: 抽象類不能建立物件
    
    Dog dog;   //ok,可以宣告抽象類的指標
    Cat cat;   //ok,可以宣告抽象類的指標
   
    printf("\n");

    Animal *animal = &dog;
    animal->shout();
    animal->impl();
   
    printf("\n");
   
    animal = &cat;
    animal->shout();
    animal->impl();
   
    printf("\n");
}

int main()
{
    test_func();

    while(1);  
}


//result:
/*
Animal construct!
Dog construct!
Animal construct!
Cat construct!

Dog: wang! wang! wang!
Dog: implement of Dog!

Cat: miao! miao! miao!
Cat: implement of Cat!

Cat destory!
Animal destory!
Dog destory!
Animal destory!
*/
(YC:程式碼已除錯無誤)


四. 巧用純虛解構函式實現介面類
c++不像java一樣有純介面類的語法,但我們可以通過一些手段實現相同的功能。

(1)能不能用“protected”實現介面類?

看如下程式碼:

#include <stdio.h>


class A
{
protected:
	virtual ~A()
	{
		printf(" A: 解構函式  \n");
	}
};
class B : public A
{
public:
	virtual ~B()
	{
		printf(" B: 解構函式  \n");
	}
};
int _tmain(int argc, _TCHAR* argv[])
{
	//A* p1 = new A;              //error:[1]有問題
	//delete p1;


	B* p2 = new B;		     //ok:[2]沒問題,輸出結果為:
	delete p2;  		     /* B: 解構函式
                                        A: 解構函式*/(注意此處還是會呼叫A的解構函式的,不過編譯沒問題)
                 
	//A* p3 = new B;
	//delete p3;                 //error:[3] 有問題


	return 0;
}

通過在類中,將類的建構函式或者解構函式申明成protected ,可以有效防止類被例項話,要說實用的話,建構函式是protected更有用,肯定能保證類不會被例項化,而如果解構函式是protected的話,建構函式不是protected的話,還可能存在編譯通過的漏洞,如下:

Case1:

class A
{
protected:
	A()
	{
		printf(" A: A()  \n");
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	A* p1 = new A;                //編譯不通過,無法訪問protected建構函式
	delete p1;

	return 0;
}

Case2:

class A
{
protected:
	~A()
	{
		printf(" A: ~A()  \n");
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	A* p1 = new A;                //編譯通過,此時因為僅僅是用到了A的建構函式,還不需要它的解構函式
	return 0;
}

(附:如果將main中改為:
int _tmain(int argc, _TCHAR* argv[])
{
	A a;
	return 0;
}
則編譯出錯,提示無法訪問protected成員A::~A().兩種情況出現差異的原因是什麼?

)

Case3:

class A
{
protected:
	~A()
	{
		printf(" A: ~A()  \n");
	}
};
int _tmain(int argc, _TCHAR* argv[])
{
	A* p1 = new A;                
	delete p1;                //編譯失敗,因為編譯器發現A的解構函式是protected
	return 0;
}

所以,一種可行的辦法貌似是這樣的:

class A
{
protected:
	virtual ~A()
	{
		printf(" A: ~A()  \n");
	}
};

class B : public A
{
};

int _tmain(int argc, _TCHAR* argv[])
{
	B* p =new B;       //ok:這種情況下確實是可行的(YC:仔細看會發現這種情況同“(1)看如下程式碼”下面的程式碼中ok的情況相同)
	delete  p;
	return 0;
}

由於B public繼承自A,所以其可以完全訪問A的構造或解構函式,但是:

int _tmain(int argc, _TCHAR* argv[])
{
    A* p =new B;
    delete  p;                //error:由於p變成指向A的指標,字面上編譯器需要知道A的解構函式,然後A的解構函式又是protected
    return 0;
}

即便像這樣B顯示過載了A的解構函式:

class A
{
protected:
	virtual ~A()
	{
		printf(" A: ~A()  \n");
	}
};
class B : public A
{
public:
	virtual ~B()
	{
		printf(" B: ~B()  \n");
	}
};
int _tmain(int argc, _TCHAR* argv[])
{
	A* p =new B;
	delete  p;        //error:也還是不行,因為過載是執行時的事情,在編譯時編譯器就認定了A的解構函式,結果無法訪問
	return 0
}

小結:

貌似用protected這樣的方法並不是很恰當,雖然在遵守一定規則的情況下確實有他的實用價值,但並不是很通用

(2)應該怎樣實現介面類?

其實上面protected的思路是對的,無非是讓父類無法例項化,那麼為了讓父類無法例項化,其實還有一個方法,使用純虛擬函式

class A
{
public:            //這裡就不用protected了
    virtual ~A() = 0;
};
class B : public A
{
};
int _tmain(int argc, _TCHAR* argv[])
{
    B* p =new B;
    delete  p;		//編譯ok,連結error
    return 0;
}

這樣寫貌似不錯,以往大家都把類中的一般成員函式寫成純虛的,這次將解構函式寫成純虛的,更加增加通用性,編譯也通過了,但就是在連結的時候出問題,報錯說找不到A的解構函式的實現,很顯然嘛,因為A的析構是純虛的嘛。

那麼如何修改上述程式碼可以達到既可以去除上述error,又可以讓基類不能被例項化呢?如下所示:

class A
{
public:					//這裡就不用protected了
	virtual ~A() = 0                //它雖然是個純虛擬函式,但是也可以被實現
	{                               //這個語法很好很強大(完全是為了實現其介面類而弄的語法吧)
		printf(" A: ~A()  \n");
	}
};
class B : public A
{
};
int _tmain(int argc, _TCHAR* argv[])
{
	B* p =new B;
	delete  p;
	A* p2 =new B;
	delete  p2;            //不用擔心編譯器報錯了,因為此時A的解構函式是public
	return 0;
}
//result:
/*
 A: ~A()
 A: ~A()
*/ 

如此終於大功告成了,注意,不能將建構函式替代上面的解構函式的用法,因為建構函式是不允許作為虛擬函式的

補充:以上那個語法就真的只是為了這種情況而存在的,因為一般我們在虛類中申明的介面:

virtual foo()= 0;

virtual foo()= 0 {}

這兩種寫法是完全沒有區別的純虛擬函式的預設實現,僅僅在它是解構函式中才有意義!!!

所以可以說,老外是完全為了這一個目的而發明了這種語法...

最終的介面類

classInterface
{
public:       
    virtual ~Interface() = 0 {}
};
應該挺完美的了吧
[備註:內容多收集於網路~]


相關推薦

C++中基函式為什麼要virtual函式【轉】

(轉自:https://blog.csdn.net/iicy266/article/details/11906457) 知識背景          要弄明白這個問題,首先要了解下C++中的動態繫結。&n

C++中基函式為什麼要virtual函式

知識背景          要弄明白這個問題,首先要了解下C++中的動態繫結。  正題          直接的講,C++中基類採用virtual虛解構函式是為了防止記憶體洩漏。具體地說,如果派生類中申請了記憶體空間,並在其解構函式中對這些記憶體空間進行釋放

[收集]c++抽象虛擬函式以及函式實現介面

在Java、C#中有關鍵詞abstract指明抽象函式、抽象類,但是在C++中沒有這個關鍵詞,很顯然,在C++也會需要只需要在基類宣告某函式的情況,而不需要寫具體的實現,那C++中是如何實現這一功能的,答案是純虛擬函式。 含有純虛擬函式的類是抽象類,不能生成物件,只能派生。

從零開始學C++之虛擬函式與多型(一):虛擬函式表指標函式object slicing與虛擬函式C++物件模型圖

#include <iostream>using namespace std;class CObject {public:     virtual void Serialize()     {         cout << "CObject::Serialize ..." <&

C++ 第六章(多型性和虛擬函式)下篇(函式虛擬函式

一,虛解構函式 如果用new運算子建立了臨時物件,若基類中有解構函式並且定義了一個指向該基類的指標變數。在程式用帶指標引數的delete運算子撤銷物件時,會發生:系統只執行基類的解構函式,而不執行派生類的解構函式。 #include<iostream> using

C++學習:虛擬函式,虛擬函式(virtual),繼承,函式

C++學習:虛擬函式,虛繼承,純虛擬函式(virtual)虛解構函式 虛擬函式 純虛擬函式 虛解構函式 虛繼承 簡介 在java這種高階語言中,有abstract和interface這兩個關鍵字.代表的是抽象類和介面,但是在C++這門語言中

函式虛擬函式

虛解構函式 通過基類的指標刪除派生類物件時,通常情況只調用基類的解構函式 但是,刪除一個派生類物件時,應該先呼叫派生類的解構函式,然後呼叫基類的解構函式(構造時自頂向下,析構時自底向上) 這種情況會產生記憶體洩漏,最終導致系統應可用記憶體不足而崩潰 解決辦法 把基類的解構函式宣告為virtual,此

函式虛擬函式考題

虛解構函式、虛擬函式結合考題變種 1.[Effective C++原則07]:為多型基類宣告virtual 解構函式。 [如果不]: 如果不宣告為解構函式,可能出現的結果如下:Derived物件的成分沒有被銷燬,形成資源洩露、在除錯上會浪費很長時間。 #incl

C++進階--派生的析(函式,shared_ptr)

//############################################################################ /* 在多型虛基類中宣告一個虛解構函式 */ /* 為什麼需要虛解構函式 */ class yellowdog : public dog { };

c++虛擬函式表與函式

由於本人才疏學淺,本文難免存在遺漏之處,歡迎大家留言指正,本人將感激不盡。 C++虛擬函式表與虛解構函式 1.靜態聯編和動態聯編 聯編:將原始碼中的函式呼叫解釋為要執行函式程式碼。 靜態聯編:編譯時能確定唯一函式。 在C中,每個函式名都能確定唯一的函式程式碼。

C++學習之多型篇(虛擬函式函式實現原理--虛擬函式表)

通過下面的程式碼來說明: #include <iostream> #include <stdlib.h> #include <string> using namespace std; /**  *  定義動物類:Animal  *  成員

c++函式

純虛解構函式和虛解構函式的區別在於純虛解構函式一般用於將類定義為抽象類,這時候有同學會問啦?抽象類不是定義一般成員方法的嗎?原因是這樣的,當我們在類裡面實在找不到可以定義為純虛擬函式的成員方法的時候,這時候可以將解構函式定義為純解構函式;純虛構函式的實現需要注意以下幾個地方:

C++繼承多型,成員函式(包括函式複製建構函式)學習筆記

通過哺乳類派生貓、狗等學習繼承、多型中的知識點 先貼上類的程式碼 #include<iostream> enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERAMN, LAB };//犬種 class Mam

深入探索C++物件模型(九) 函式以及顯式定義的函式問題函式Rules of Three)

  如果類沒有定義解構函式,那麼只有類中含有成員物件(或者本類的基類)擁有解構函式的情況下,編譯器才會合成一個出來,否則解構函式被視為不要,也就不需要合成。例如,如下類,雖然Point類擁有虛擬函式:class Point {  piblic:       Point(flo

C++ 函式函式的區別

先看下面程式碼: class Parent { public: Parent(); ~Parent(); }; Parent::Parent() { cout << "基類構造...." << endl << endl; } Parent::

函式(派生與基

1.情況1, class Base { public: ~Base() { cout << "~Base()" << endl; } }; class Derived1 : public Base { public: Derived1():name_

函式函式的作用

1 虛解構函式 作用: 當使用基類的指標刪除一個派生類的物件時,可以呼叫派生類的解構函式。由於有虛擬函式表的存在,這裡就發生了多型,使得派生類的解構函式能夠被呼叫。反之,如果基類的解構函式不是虛擬函式,則使用基類的指標刪除派生類的物件時,不會呼叫派生類的解構函式

C++之繼承(多重繼承+多繼承+繼承+函式+重定義)

多重繼承和多繼承 這個我們來講講這兩個的概念問題,一字之差,千差萬別。 多重繼承,比如有三個類,人類-士兵類-步兵類,三個依次繼承,這樣的繼承稱為多重繼承。 class Person {}; class Soldier :public Person

C++多型及函式

C++實現多型 多型:支援相關的物件具有不同的成員函式(但原型相同) ,並允許物件與適當的成員函式進行執行時繫結。 虛表和虛表指標介紹 當呼叫一個虛擬函式時,被執行的程式碼必須與

函式 和 建構函式中最好不要呼叫虛擬函式

參考Effective c++ 條款7 和調款9 條款7: 多型性質的基類虛解構函式的重要性! 1、帶多型性質的 base classes應該宣告一個virtual 解構函式, 如果class帶有任何virtual函式,它就應該擁有一個virtual解構函