1. 程式人生 > >C++中定義一個不能被繼承的類(友元類+類模板)

C++中定義一個不能被繼承的類(友元類+類模板)

自從C++11標準出來之後,就有了關鍵字final可以直接宣告一個類不能被繼承。那麼,在此之前如果想要一個類不能被繼承,可能還需要下一番功夫。

文章目錄


原文地址:https://www.qingdujun.com/zh-CN/cpp-class-cannot-be-inherited.html


1.宣告建構函式為私有

如果將建構函式、虛構函式宣告為私有的,那麼這個類肯定不能被繼承。

class A {
private:
	A() {}
	~A() {}
};

存在的問題
雖然這樣的類不能被子類繼承,但是本身也無法建立物件。為了解決該問題,可以使用借鑑單例模式的方式,在類中建立好物件。
由於這裡講的不是單例模式,也不追究這個例子是否符合多執行緒訪問了。

class A {
public:
	static A* getInstance() {
		if (!s_inst) {
			s_inst = new A();
		}
		return s_inst;
	}
private:
	A() {}
	~A() {}
	static A* s_inst;
};
A*
A::s_inst = nullptr;

並不符合要求
這種被限制了的類,顯然不是我們想要的。那麼如果再改進一下,以滿足要求呢?

2.子類宣告為基類的友元類

上面很明確的說明了,建構函式為私有的情況下是無法被繼承的。但是有一種方法,可以用“友元類”來打破它。

class B;//Class declaration

class A {
private:
	A() {}
	~A() {}
	friend B;
};

class B : public A {
public:
	B() {}
	~B() {}
};

以上這種寫法有一個問題,就是我們改變了“基類A”讓它可以被繼承。
但是也引入了另一個問題——其他類也可以繼承“類B”了。

而且我們的目的是寫一個不能被繼承的類,那還有沒有其他的方法呢?

3.虛繼承——子類直接呼叫虛基類的建構函式(私有)

既然基類建構函式為私有的,肯定不能直接繼承,這一點是確定的。上一節中,既然“類B”可以被繼承,因為它的建構函式是公有的。

那麼還有一種方式是“虛繼承”。

我們知道“虛繼承”有一個特性,一般而言,初始化時子類都會呼叫直接基類的建構函式從而先初始化基類,但是C++“多繼承”時為了避免重複初始化問題,引入了“虛繼承”概念————它使用了一套,讓最遠的子類直接初始化虛基類,其父類不再初始化基類,而避免了重複初始化問題。

class B;//Class declaration

class A {
private:
	A() {}
	~A() {}
	friend B;
};

class B : virtual public A {
public:
	B() {}
	~B() {}
};

class C : public B { //error
public:
	C() {

	}
};

對上面的程式碼解釋一下,由於“類B”虛繼承自“類A”,而“類C”又繼承自“類B”。
那麼就會由“類C”直接呼叫“類A”的建構函式,這一步就失敗了…從而造成了“類B”和“類A”這一個整體類不能被繼承。

那麼,“類C”是否可以“虛繼承”自上面這個整體類呢?

答案也是否定的,“類B”之所以可以虛繼承“類A”————那是因為聲明瞭“友元類”。
很顯然,就算“類C”虛繼承以上的任意一個類,都是不可以的,因為不管繼承誰,都會由“類C”自己負責呼叫“類A”的建構函式,這一步始終無法做到。

4.類模板——不失一般性

為了不失一般性,這裡引入“類模板”概念。引入該概念之後,“類B”繼承“類A”的時候一定要指明型別(比如,A<B>)。

template <typename T>
class A {
	friend T;
private:
	A() {}
	~A() {}
};

class B : virtual public A<B> {

};

那麼,這裡最終的“類B”是一個不能被繼承的類。但是注意,“類A”卻變的是可以被繼承的。


©qingdujun
2018-12-7 北京 海淀


References:

[1] C++ Primer(第五版)
[2] C++中定義一個不能被繼承的類
[3] 面試題28:不能被繼承的類
[4] c++設計一個不能被繼承的類,為什麼必須是虛繼承?原因分析
[5] C++ 類模板和模板類