【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();
懶漢與餓漢模式的選擇:
- 由於要進行執行緒同步,所以在訪問量比較大,或者可能訪問的執行緒比較多時,採用餓漢實現,可以實現更好的效能。這是以空間換時間。
- 在訪問量較小時,採用懶漢實現。這是以時間換空間。
單例模式的適用場景
- 系統只需要一個例項物件,或者考慮到資源消耗的太大而只允許建立一個物件。
- 客戶呼叫類的單個例項只允許使用一個公共訪問點,除該訪問點外不允許通過其它方式訪問該例項 (就是共有的靜態方法)。