1. 程式人生 > >【C++】設計一個類?

【C++】設計一個類?

1、設計一個類,不能被繼承:

   我們知道派生類在構造物件時,會先呼叫其基類的建構函式,然後再呼叫派生類的建構函式。所以,如果我們把基類的建構函式和解構函式設計為私有的,那麼派生類就不能呼叫基類的構造函數了,自然也就不能繼承了。但是這樣的話,這個基類也不能例項化了。我們可以想到通過靜態方法,通過一個靜態方法來返回類的例項,另一個靜態方法來釋放該物件。程式碼如下:


////將基類的建構函式和解構函式設為私有的( static方法可以解決例項化問題)
//基類不能被繼承,同時也解決了例項化問題,但只能是在堆上分配記憶體
class AA
{
private:
	AA() { cout << "AA()" << endl; }
	~AA() { cout << "AA()" << endl; }
public:
	static void test(AA* a)
	{
		cout << "AA::test()" << endl;
	}
	static AA* construct(int n)
	{
		AA *a = new AA();
		a->_a = n;
		return a;
	}
	static void destroy(AA *aa)
	{
		delete aa;
	}
private:
	int _a;

};
class BB :public AA
{

};
void test()
{
	//BB b;
	AA *a = AA::construct(3);
	AA::test(a);
	AA::destroy(a);
}

C++11中的final關鍵字:

//C++11裡面的final關鍵字
class Base //final(修飾類時,該類不能被繼承)
{
public:
	virtual void fun()
	{
		cout << "Base::fun()" << endl;
	}
};
class Derived :public Base
	{
	public:
		void fun()
	{
		cout << "Base::fun()" << endl;
	}
	};

 有什麼辦法可以讓類既不能被繼承,又可以在棧上和堆上都能建立物件呢?不妨考慮一下友元
- 先建立一個基類A,將其建構函式和解構函式都宣告為私有的;
- 我們要求的不能被繼承的類B宣告為A的友元類,這樣B可以訪問A類的建構函式和解構函式,B可以正常構造;
- 同時還需要讓類B虛擬繼承A類,此時B類的子類C在構造物件時,會直接呼叫A類的建構函式,但是由於友元關係是不能被繼承的,所以,C類呼叫A類的建構函式會報錯,也就是說C類不能成功構造出物件,所以,B類是不可以被繼承的。程式碼如下:


//類不能被繼承,但可以在棧上和堆上建立物件:(友元)
//1、先建立一個基類A,將其構造和解構函式都宣告為私有的;
//2、將不能被繼承的類B宣告為A的友元類,這樣B可以訪問A類的構造解構函式,B可以正常構造;
//3、同時還需要類B虛擬繼承A類,此時B類的子類C在構造物件時,會直接呼叫A類的建構函式,但是
//友元關係是不能被繼承的,所以C類不能成功的構造出物件,所以B類是不可以被繼承的。

template<class T>
class A
{
	friend T;
private:
	int a;
	A()
	{
		cout << "A()" << endl;
	}
};

class B :public virtual A<B>
{
public:
	B()
	{
		cout<<"B()" << endl;
	}
};
class C :public B
{
public:
	C()  //友元不會繼承,編譯出錯
	{
		cout << "C()" << endl;
	}
};
int main()
{
	B b;
	C c;
	return 0;
}

      這裡需要說明的是:我們設計的不能被繼承的類B對基類A的繼承必須是虛繼承,這樣一來C類繼承B類時會去直接呼叫A的建構函式,而不是像普通繼承那樣,先呼叫B的建構函式再呼叫A的建構函式;C類直接呼叫A類的建構函式,由於A類的建構函式是私有的,而B是A的友元,C類不是A的友元,友元關係不會繼承,因此會編譯報錯。除此之外,在C++11中已經像Java一樣有了final關鍵字,被final修飾的虛擬函式(**只能是虛擬函式**)不能被過載,被final修飾的類不能被繼承。

2、設計一個類,只能在棧上建立物件?

       使用new運算子,物件會建立在堆上,也就是說只要不用new去建立物件就可以實現,我們知道new和delete分別呼叫了operator new和operator delete,如果我們把這兩個函式宣告為私有的,操作符new就不能用了。


////設計一個類,只能在棧上建立物件?
//不用new去建立物件,將operator new和operator delete設為私有的。操作符new就不能用了。

class A
{
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	void *operator new(size_t size){};
	void operator delete(void *ptr) {};
};
int main()
{
	A a;
	return 0;
}

3、設計一個類,只能在堆上建立?

    1. 使用new運算子,物件就可以在堆上建立。如果我們將建構函式和解構函式定義為protected(可以讓類被繼承),然後定義兩個公有的靜態函式呼叫new和delete,來建立和銷燬物件。

      2.將解構函式宣告為私有的。 物件建立在棧上面時,是由編譯器分配空間的,呼叫建構函式來構造物件,編譯器釋放物件,編譯器管理了物件的整個生命週期,編譯器為物件分配空間的時候,只要是非靜態的函式都會檢查,包括解構函式,如果解構函式不可訪問,編譯器就無法呼叫類的解構函式來釋放記憶體,那麼編譯器將無法在棧上為物件分配記憶體。


//設計一個類,只能在堆上建立物件?
//方法一 使用new運算子,物件就可以在堆上建立
class A
{
protected:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
public:
	static A* _Construct()
	{
		return new A();
	}
	static void Destroy(A *p)
	{
		delete p;
		p = NULL;
	}
};
int main()
{
	A* p = A::_Construct()
		;
	A::Destroy(p);
}

//方法二 將解構函式宣告為私有的
class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	void Destroy()
	{
		delete this;
	}
private:
	~A()
	{
		cout << "~A()" << endl;
	}
};
int main()
{
	A* a = new A();
	(*a).Destroy();
}

4、設計一個類,只能有一個例項?

- 為了防止從類的外部呼叫建構函式,產生類的新的例項,我們應該把類的建構函式宣告為protected或private
 - 由於只能生成一個類的例項,我們考慮用靜態函式來記錄,到底之前有沒有構造過類的例項
 - 如果沒有構造過,那麼就構造一個新的例項,如果構造過,那麼就將之前建立的例項返回。
 - 為了保證之前構造的例項,在程式執行期間一直存在,不被析構,我們只能把指向這個例項的指標宣告成靜態變數,存放在靜態區,把這個類的例項用new來構造,並放在堆中。
單例模式的特點:
1、一個類只能有一個例項。
2、一個類必須自己建立自己的唯一例項。
3、一個類必須給所有其他物件提供這一例項。

懶漢式單例模式:在類載入時,不建立例項,因此類的載入速度快,但執行時獲取物件的速度慢。第一次使用的時候載入:

//懶漢單例模式(類的載入速度快,執行時獲取物件慢)
class singleton
{
private:
	singleton()
	{
		cout << "singletion()" << endl;
	}
	singleton(const singleton &s);
	singleton& operator = (const singleton& s);
public:
	static singleton* getInstance()
	{
		if (_instance == NULL)
			_instance = new singleton();
		return _instance;
	}
	static void Release()
	{
		if (NULL != _instance)
		{
			delete _instance;
			_instance = NULL;
		}
	}
private:
	static singleton* _instance;
};
singleton* singleton::_instance = NULL;
int main()
{
	singleton* p = singleton::getInstance();
	return 0;
}

相關問題: 懶漢模式是否執行緒安全?
 - 不安全,第一次呼叫時new,假如兩個執行緒都是第一次呼叫,可能new了兩次
>如何改成執行緒安全的?
 - 只有第一次呼叫才會有執行緒不安全的問題,if前後加鎖,後面的執行緒被掛起等待,效率問題。
 - 加鎖的外面在加一層if,不用再加鎖解鎖了;保證了執行緒安全和效率問題。
 - volatile修飾inst,如果沒有從記憶體讀資料,而是從暫存器讀取資料,還是有問題。

餓漢單例模式:在類載入時完成了初始化,所以類載入比較慢,但獲取物件的速度快。模組載入的時候初始化,影響程式啟動時間:


//餓漢單例模式(類載入比較慢,但獲取物件的速度快)
class singleton
{
private:
	singleton()
	{
		cout << "singletion()" << endl;
	}
public:
	static singleton* getInstance()//不用同步(類載入時已經初始化,不會有多執行緒的問題)
	{
		return &_instance;
	}
private:
	static singleton _instance;
};
singleton* singleton::_instance = new singleton();
int main()
{
	singleton* p = singleton::getInstance();
	return 0;
}
template<class T>
class S
{
	static T* Instance()
	{
		return &inst;
	}
	static T inst;
};
T* ptr = S::Instance();

懶漢與餓漢模式的選擇:

  • 由於要進行執行緒同步,所以在訪問量比較大,或者可能訪問的執行緒比較多時,採用餓漢實現,可以實現更好的效能。這是以空間換時間。
  • 在訪問量較小時,採用懶漢實現。這是以時間換空間。

單例模式的適用場景

  • 系統只需要一個例項物件,或者考慮到資源消耗的太大而只允許建立一個物件。
  • 客戶呼叫類的單個例項只允許使用一個公共訪問點,除該訪問點外不允許通過其它方式訪問該例項 (就是共有的靜態方法)。