1、允許一個或零個對象: 把所有的構造函數聲明為私有,則不能建立任何對象。如果允許隻建立一個對象,可用一個全局函數(或靜态成員函數)來建立唯一的一個靜态對象,并傳回其引用,為提高效率,可把全局函數聲明為inline。注意這個全局函數要聲明為類的友元函數,因為要使用私有的構造函數。
例如我們想建立一個列印機對象,但希望實際上隻有一個列印機,可以這樣寫:
//printer.hpp:隻允許建立一個列印機對象,即單例模式
#ifndef PRINTER_HPP
#define PRINTER_HPP
class PrintJob{
public:
PrintJob(const string& whatToPrint){
//...
}
//...
};
class Printer{ //列印機類
private:
//所有構造函數都聲明為私有,則不能作為基類使用
Printer(){
//...
}
Printer(Printer const& rhs){
//...
}
friend Printer& thePrinter();
//...
public:
void submitJob(PrintJob const& job){
//...
}
void reset(){
//...
}
void performSelfTest(){
//...
}
//...
};
inline Printer& thePrinter(){ //擷取唯一的一個列印機對象
static Printer p;
return p;
}
//...
#endif
這裡我們把thePrinter()實作為一個全局函數,裡面隻建立一個靜态對象,傳回其引用,這樣用戶端代碼中的所有thePrinter()調用都隻使用這個唯一的靜态對象。注意,對構造函數,可以隻聲明為私有,如果不使用它,則可以不提供定義,但對需要被使用的構造函數(因為是私有,隻能在類的成員函數或友元函數中使用)則必須提供定義。當然,我們也可以把thePrinter()實作為Printer類的靜态成員函數,這樣就不需要友元聲明了。但使用全局函數也有優勢,因為在某些類中聲明的靜态對象即使沒用到也會被構造(以及析構)出來,而全局函數裡的靜态對象隻有執行函數時才被建立。
另一方面,我們有時候會把那個唯一的p對象聲明為全局的靜态對象或Printer類的靜态成員,然後在thePrinter()或Printer::thePrinter()中直接傳回它,這不是一種好的實作方案。我們稱函數内的static對象稱為local static對象,其他的static對象(class作用域、namespace作用域、global作用域、file作用域)稱為non-local static對象。C++隻保證在一個特定編譯單元内的靜态對象的初始化順序,對不同編譯單元内的non-local static對象的初始化次序沒有明确定義。可見這樣的實作方案會使得p對象的初始順序不明确。
當我們需要用到non-local static對象,又要保證正确的初始化順序時,解決方法恰恰就是将non-local static對象放在一個inline包裝函數中,函數傳回一個引用指向此對象(變成local static對象)。用函數調用來替換直接通路non-local static對象,這時函數内的局部靜态對象就會有确定的初始化順序(在第一次執行函數時初始化)。注意,我們通常在設計時并不建議傳回指向函數内局部對象的reference,但這裡的包裝函數行為單純,隻是包裝一個static對象并傳回其reference,并不做其他工作。當多次調用包裝函數時,reference指向同一個static對象,并且值均相同(包裝函數内并沒有改變對象的成員值),是以這樣實作沒有副作用。
注意,構造函數聲明為私有的類不能作為基類使用,也不能組合到其他類中來使用。我們可以放寬一點,把構造函數聲明為protected的,這樣類同樣不可以建立對象,但可以被繼承。
2、允許建立任意數量的對象,但不允許作為基類: 把構造函數聲明為私有,這樣就不允許作為基類了。同時我們提供相應的僞構造函數,用它來建立對象并傳回。例如對有限狀态機類,如下:
//fsa.hpp:有限狀态機類,允許建立任意數量的對象,但不允許作為基類
#ifndef FSA_HPP
#define FSA_HPP
class FSA{ //有限狀态機類
private:
//構造函數聲明為私有
FSA(){
//...
}
FSA(FSA const& rhs){
//...
}
public:
static FSA* makeFSA(){ //僞構造函數
return new FSA();
}
static FSA* makeFSA(FSA const& rhs){ //僞拷貝構造函數
return new FSA(rhs);
}
//...
};
#endif
用戶端使用這個類時,就必須調用僞構造函數來建立對象。當然,由于對象是動态配置設定的,這時客戶必須自己調用delete來删除對象。
3、允許對象個數限制在某個給定的值: 使用前面在“模闆與繼承相結合的威力”中介紹的奇異遞歸模闆模式技術,編寫一個具有執行個體計數功能的基類即可,讓需要計數的類繼承這個類。因為類要實作計數功能,是一種“實作”的關系,故最好用私有繼承,表示用這個基類來實作需要的功能。這裡我們做一下修改,當需要計數的類對象個數超過上限時就抛出異常。注意不同的類可以通過特化這個基類的表示上限值的成員,以滿足不同類對象個數的要求。
//objectcounter.hpp:對對象建立進行記數的模闆
#ifndef OBJECT_COUNTER_HPP
#define OBJECT_COUNTER_HPP
#include <cstddef>
template<typename T>
class ObjectCounter{ //用來記錄T型對象構造的總個數
private:
static std::size_t numObjects;//存在對象的個數
static const std::size_t maxObjects; //對象個數的上限,由需要計數的類來指定
void incr(){
if(ObjectCounter<T>::numObjects>=ObjectCounter<T>::maxObjects)
throw TooManyObjects();
++ObjectCounter<T>::numObjects;
}
protected:
ObjectCounter(){ //預設構造函數
incr();
}
ObjectCounter(ObjectCounter<T> const& rhs){ //拷貝構造函數
incr();
}
~ObjectCounter(){ //析構函數
--ObjectCounter<T>::numObjects;
}
public:
class TooManyObjects{ }; //異常類
static std::size_t objectCount(){ //傳回目前對象個數
return ObjectCounter<T>::numObjects;
}
};
//static成員變量必須在類外進行定義,由于是非const的,是以也要類外初始化
template<typename T>
std::size_t ObjectCounter<T>::numObjects=0;
#endif
//mystring.hpp:使用了對象計數的MyString類
#ifndef MYSTRING_HPP
#define MYSTRING_HPP
#include "objectcounter.hpp"
class MyString : private ObjectCounter<MyString>{
public:
//由于是私有繼承,要用using把objectCount函數變成公有
using ObjectCounter<MyString>::objectCount;
//...
};
//特化ObjectCounter的maxObjects成員,值為3,以限制MyString類對象的個數,
//這個語句必須加入到MyString類的實作檔案中
template<>
const std::size_t ObjectCounter<MyString>::maxObjects=3;
#endif
//countprinter.hpp:使用了對象計數的Printer類
#ifndef PRINTER_HPP
#define PRINTER_HPP
#include "objectcounter.hpp"
class Printer : private ObjectCounter<Printer>{
public:
//由于是私有繼承,要用using把objectCount函數變成公有
using ObjectCounter<Printer>::objectCount;
//...
};
//對Printer,對象個數特化為值4
template<>
const std::size_t ObjectCounter<Printer>::maxObjects=4;
#endif
//countertest.cpp:用戶端代碼,對對象計數進行測試
#include <iostream>
#include "mystring.hpp"
#include "countprinter.hpp"
int main(){
MyString s1,s2;
std::cout<<"number of MyString: "
<<MyString::objectCount()<<std::endl;
std::cout<<"number of MyString: "
<<s1.objectCount()<<std::endl;
s1.~MyString(); //減少一個對象
std::cout<<"number of MyString: "
<<MyString::objectCount()<<std::endl;
try{
MyString s3,s4,s5; //對象個數超過上限,會抛出出異常
}catch(ObjectCounter<MyString>::TooManyObjects& e){
std::cout<<"Too many MyString objects."<<std::endl;
}
Printer t1,t2,t3;
std::cout<<"number of Printer: "
<<Printer::objectCount()<<std::endl;
std::cout<<"number of Printer: "
<<t1.objectCount()<<std::endl;
Printer t4,t5; //對象個數超過上限,會抛出出異常,終止程式
return 0;
}
這裡ObjectCounter<T>模闆用于計數,它需要作為基類,成員名的前面最好加類作用域符(也可用this->),使它變成依賴型名稱。表示對象個數上限值的maxObjects成員是一個靜态常量,它并沒有初始化。我們必須在需要計數的類MyString、Printer中指定它的值。不同類的對象個數上限可能不同,是以我們隻要特化maxObjects這個成員并指定需要的值即可(類模闆可以隻特化某個成員)。在MyString、Printer這些類中,由于是私有繼承,是以要用using聲明把objectCount函數變成公有,這樣就可以用它來擷取目前對象的個數。