1. 程式人生 > >用c++設計一個不能被繼承的類

用c++設計一個不能被繼承的類

分析:這是Adobe 公司2007 年校園招聘的最新筆試題。這道題除了考察應聘者的C++ 基本功底外,還能考察反應能力,是一道很好的題目。
在Java 中定義了關鍵字final ,被final 修飾的類不能被繼承。但在C++ 中沒有final 這個關鍵字,要實現這個要求還是需要花費一些精力。
首先想到的是在C++ 中,子類的建構函式會自動呼叫父類的建構函式。同樣,子類的解構函式也會自動呼叫父類的解構函式。要想一個類不能被繼承,我們只要把它的建構函式和析構函 數都定義為私有函式。那麼當一個類試圖從它那繼承的時候,必然會由於試圖呼叫建構函式、解構函式而導致編譯錯誤。
可是這個類的建構函式和解構函式都是私有函數了,我們怎樣才能得到該類的例項呢?這難不倒我們,我們可以通過定義靜態來建立和釋放類的例項。基於這個思路,我們可以寫出如下的程式碼:

///////////////////////////////////////////////////////////////////////
// Define a class which can't be derived from
///////////////////////////////////////////////////////////////////////
class FinalClass1
{
public :
      static FinalClass1* GetInstance()
      {
            return new FinalClass1;
      }

      static void DeleteInstance( FinalClass1* pInstance)
      {
            delete pInstance;
            pInstance = 0;
      }

private
: FinalClass1() {} ~FinalClass1() {} };

這個類是不能被繼承,但在總覺得它和一般的類有些不一樣,使用起來也有點不方便。比如,我們只能得到位於堆上的例項,而得不到位於棧上例項。
能不能實現一個和一般類除了不能被繼承之外其他用法都一樣的類呢?辦法總是有的,不過需要一些技巧。請看如下程式碼:

///////////////////////////////////////////////////////////////////////
// Define a class which can't be derived from
///////////////////////////////////////////////////////////////////////
template <typename T> class MakeFinal
{
      friend T;

private :
      MakeFinal
() {} ~MakeFinal() {} }; class FinalClass2 : virtual public MakeFinal<FinalClass2> { public : FinalClass2() {} ~FinalClass2() {} };

這個類使用起來和一般的類沒有區別,可以在棧上、也可以在堆上建立例項。儘管類 MakeFinal 的建構函式和解構函式都是私有的,但由於類 FinalClass2 是它的友元函式,因此在 FinalClass2 中呼叫 MakeFinal 的建構函式和解構函式都不會造成編譯錯誤。
但當我們試圖從 FinalClass2 繼承一個類並建立它的例項時,卻不同通過編譯。

class Try : public FinalClass2
{
public :
      Try() {}
      ~Try() {}
};

Try temp;
由於類 FinalClass2 是從類 MakeFinal 虛繼承過來的,在呼叫 Try 的建構函式的時候,會直接跳過 FinalClass2 而直接呼叫 MakeFinal 的建構函式。非常遺憾的是, Try 不是 MakeFinal 的友元,因此不能呼叫其私有的建構函式。
基於上面的分析,試圖從 FinalClass2 繼承的類,一旦例項化,都會導致編譯錯誤,因此是 FinalClass2 不能被繼承。這就滿足了我們設計要求。

從另外一篇文章裡面copy過來:
如果大家熟悉java的話應該知道java中有一種類不能被繼承,那就是final類.這種類有很多用處,尤其是在大的專案中控制類的繼承層次. 使子類數量不至於爆炸.在使用了多繼承的類層次中這也是防止出現菱形繼承層次結構的一個好辦法. 要實現一個不能被繼承的類有很多方法.

  如何使類不能被繼承呢?主要的思路就是使子類不能構造父類的部分,這樣子類就沒有辦法例項化整個子類.這樣就限制了子類的繼承. 所以我們可以將父類的建構函式宣告成為私有的,但是這樣父類不就不能例項化了嗎?可以新增一個靜態幫助函式來進行構造. 雖然這樣很簡陋.但是這的確是一種解決方法.

  可是如果只有這個方法能夠解決,那麼C++實在是太不靈活了.而且這也不值得寫一片文章出來!有沒有辦法解決上面的方法中的那些問題呢?

  當然有!我們可以利用友員不能被繼承的特性!

  首先假設已經有一個類CXX.這是某一個類層次的分支,我們現在要從CXX繼承一個Final子類CParent來,也就是CParent不能夠被繼 承. 我們可以充分利用友員不能被繼承的特點,也就是說讓CParent是某一個類的友員和子類,CParent可以構造,但是CParent的子類 CChild確不能繼承那個友員特性,所以不能被構造.所以我們引入一個CFinalClassMixin.
任何類從它繼承都不能被例項化
同時這個類本身我們也不希望它被例項化.
如何實現這個類那?很簡單!那就是實現一個建構函式和解構函式都是private的類就行了.同時在這類裡面將我們的CParent宣告為友員. 程式碼如下:

class CFinalClassMixin
{
friend class CParent;
private:
CFinalClassMixin(){}
~CFinalClassMixin(){}
};

//我們的CParent程式碼應該如下:
class CParent:publicCXXX
{
public:
CParent(){}
~CParent(){}
};

  它是從CXXX擴充套件的一個類(注,此時它還是能夠被繼承).現在我們需要它不能被繼承.那麼只要將程式碼改成

class CParent:public CFinalClassMixin, public CXXX
{
public:
CParent(){}
~>CParent(){}
};

就行了.現在從CParent繼承一個子類試試

class CChild:public CParent{};

編譯一下程式碼試試,發現:竟然沒有作用!!

靠,這是為什麼!

  現在再回想一下我們這麼操作的原因,也就是這個方案的原理,那就是讓父類可以訪問Mixin類的建構函式,但是子類不能訪問.

  現在看看我們的程式碼,發現父類是CFinalClassMixin類的友員,可以訪問它的建構函式.因為友員不能繼承,所以CChild不能訪問CFinalClassMixin的建構函式.所以應該不能被例項化.

  CChild的確不能訪問CFinalClassMixin的建構函式,但是它卻不必呼叫它!我想這就是問題的原因所在.CChild是通過CParent來構造CFinalClassMixin的,所以這個友員對他並沒有什麼用處!

  現在問題找到了.要解決很簡單.只要讓CChild必須呼叫CFinalClassMixin的建構函式就行了,怎麼才能達到目的呢?

  還記得虛繼承嗎?虛繼承的一個特徵就是虛基類的建構函式由最終子類負責構造!所以將CParent從CFinalClassMixin繼承改成從CFinalClassMixin虛繼承就可以了.程式碼如下:

class CParent:virtual public CFinalClassMixin, public CXXX
{
public:
CParent(){}
CParent(){}
};

現在試試,行了.

  但是可能有些人會對多繼承心有餘悸!但是我們這裡並沒有必要這麼擔心!為什麼?因為我們的CFinalClassMixin類是純的!pure! 也就是說它根本沒有成員變數!那麼我們就根本不用擔心多繼承帶來的最大問題.菱形繼承產生的資料冗餘.以及二義性.

現在還有個不足!那就是我們不能每次使用這個CFinalClassMixin類就在裡面加上對某個類的友員宣告啊!這多麻煩啊! 雖然不是什麼大問題,但是我覺的還是要解決,因為我充分信任C++!

  解決的方法也很簡單!那就是使用模板!具體描述就省略了,給出程式碼大家一看就知道了

下面是我得測試程式的完整程式碼(其中的CFinalClassmixin已經改成模板)

// finaltest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include
using namespace std;

template
class CFinalClassMixin
{
friend T;
private:
CFinalClassMixin(){}
~CFinalClassMixin(){}
};
class CXXX
{
public:
CXXX(){cout << "I am CXXX" << endl;}
~CXXX(){}
};
class CParent:virtual public CFinalClassMixin, public CXXX
{
public:
CParent(){}
~CParent(){}
};
class CChild:public CParent{};
int main(int argc, char* argv[])
{
CParent a; // 可以構造
//CChild b; //不能構造
return 0;
}

  現在只要對不想被繼承的類加入一個CFinalClassMixin混合類做父類就行了.

  通過限制建構函式,我們就達到了限制繼承的目的.但是這對有些還是個例外,比如全是靜態函式的類.這些類本身就不需要構造. 所以我們對它沒有辦法.但是在大多數情況下,一個全是靜態函式的類多少暗示了程式本身的設計可能是需要斟酌的.

  其實這只是Mixin類(混合類)使用的一個小小例子.還有很多其他的用處,比如UnCopiale等等.就不多說了. 我想說明的是大家可能對多繼承比較反感.但是過分否定也是得不償失的.現在對多繼承到底應不應該使用還處在爭論階段. 我覺得一個方法是否使用得當,關鍵還是在於使用的人.

相關推薦

C++設計一個不能繼承(轉)

它的 設計 指定 基於 構造 重寫 rtu 構造函數、析構函數 析構函數 在Java 中定義了關鍵字final,被final修飾的類不能被繼承。 首先想到的是在C++中,子類的構造函數會自動調用父類的構造函數。同樣,子類的析構函數也會自動調用父類的析構函數。要想一個類不能

面試題15——C++設計一個不能繼承

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

c++設計一個不能繼承

分析:這是Adobe 公司2007 年校園招聘的最新筆試題。這道題除了考察應聘者的C++ 基本功底外,還能考察反應能力,是一道很好的題目。 在Java 中定義了關鍵字final ,被final 修飾的類不能被繼承。但在C++ 中沒有final 這個關鍵字,要實

59.C++ 設計一個不能繼承

首先想到的是在C++ 中,子類的建構函式會自動呼叫父類的建構函式。同樣,子類的解構函式也會自動呼叫父類的解構函式。要想一個類不能被繼承,我們只要把它的建構函式和解構函式都定義為私有函式。那麼當一個類試圖從它那繼承的時候,必然會由於試圖呼叫建構函式、解構函式而導致編譯錯誤。

C設計一個小迷宮

#include <stdio.h> #include<conio.h> #include <windows.h> int main() { printf(“歡迎來到程式碼迷宮!\n向上:w,向下:s,向左:a,向右:d.\

C++設計封裝一個帶鬧鐘的

include #include <iostream> #include <conio.h> #include <windows.h> #include <time.h> using namespace

c#實例化繼承,必須對繼承的程序集做引用

類的屬性 結構 編譯環境 gin 是否 image 實例化 bsp class 0x00 問題 類型“Model.NewModel”在未被引用的程序集中定義。必須添加對程序集“Model, Version=1.0.0.0, Cultur

c++ 設計一個CDate

要求滿足如下要求:有帶參建構函式;可設定日期;可執行日期加一天的操作;有輸出操作(用日/月/年格式輸出日期。#include <iostream>#include <iomanip>using namespace std;class CDate{pub

C++實現一個二叉樹

/**//** 昨天參加宜搜的筆試,要求用C++寫一二叉樹類,實現插入,刪除,定位功能,輾轉再三,* 卻無從下手,甚急,終基礎不好,今天再想,通過層次遍歷二叉樹,再進行實現相關功能* 實也不難. 以下程式碼*//**//** FileName:BTree.cpp* Description:binary tre

C++實現一個日期

最近在複習C++的時候發現日期類是一個非常有用的類,在現實中是非常實用的(雖然我不知道為什麼這麼實用的類,庫裡沒有)以下是我自己實現的日期類的程式碼,因為大部分都是運算子的過載,所以理解起來應該並不難 #include <iostream> #include &

c++建立一個SUM,求二維陣列外圍各元素的和,並且輸出陣列各元素及所求之和。

具體要求如下: (1)私有資料成員 int a[4][4]:二維陣列,存放要處理的資料。 int s:存放陣列a外圍各元素的和。 (2)公有成員函式 SUM(int b[4][4]):建構函式,用陣列

C++第四周【任務3】設計一個“正整數”,並通過一系列的成員函式對其性質進行做出判斷或列出相關聯的數值。

/* (程式頭部註釋開始) * 程式的版權和版本宣告部分 * Copyright (c) 2011, 煙臺大學計算機學院學生 * All rights reserved. * 檔名稱: * 作 者:李洪懸

c++設計一個分數。要求:1.分類包含的分數運算有:連個分數的加、減、乘、除運算。

設計一個分數類。要求: 1.分數中包含的分數運算有:兩個分數的加、減、乘、除運算。 2.分數的輸出格式是:“分子/分母”。 3.編寫一個測試程式進行測試。 Java版本請複製連結檢視http://blog.csdn.net/bee0_0/article/details/7

設計一個泛型orderedCollection

ble test println 一個 不為 move stat arrays this import java.util.Arrays; /** * 設計一個泛型類orderedCollection,它存儲的Comparable對象的集合(在數組中), * 以及該集合的當

C# 實現一個簡單的 Rest Service 供外部調

message [] operation rem adk www span method title 用 C# 實現一個簡單的 Restful Service 供外部調用,大體總結為4點: The service contract (the methods it o

C#設計模式之行為模式:模板方法模式

frame 應該 ocp 方式 src 代碼復用 操作 優缺點 sse 定義(Template Method) 定義一個操作中算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。 啟示 組裝電腦一般包含三個部分,主機、顯示

C#設計模式之結構模式:裝飾模式

負責 gzip null pattern 產生 設計師 san 抽象 接口 定義(Decorator Pattern): 動態的給一個對象添加一些額外的職責。就添加功能來說,它相比生成子類更為靈活。 一、引言 在軟件開發中,我們經常想要對一類對象添加不同的功能,例如要給手

Python設計一個基於命令行的圖形界面

繪圖 結果 ssh mat intro 彩色 問題 服務 otl Introduction 如今很多開發工作都需要遠程進行,比如深度學習需要登錄到專門的服務器上。當你需要看一些可視化的結果時,可能需要用到matplotlib或是seaborn這樣的繪圖庫。那麽你或許還需要通

c實現一個跳動的小球

#include<stdio.h> #include<stdlib.h> int main() {  int x=1,y=1,dirx=1,diry=1;  for(;;)  {   int line,col;   fo

c++求一個二維整數陣列中最大子陣列之和(結對作業)

題目:返回一個二維整數陣列中最大子陣列之和。 要求: 1.輸入一個二維整形陣列,數組裡有正有負。 2.二維陣列中連續的一個子矩陣 組成一個數組,每個子陣列都有一個和。 3.求所有子陣列的和的最大值。 結對程式設計要求 兩人結對完成程式設計任務。 一人負責程式分析,程式碼程式設計。 一