一、設(shè)計(jì)模式概念
設(shè)計(jì)模式是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。
使用設(shè)計(jì)模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。
根本原因是為了代碼復(fù)用,增加可維護(hù)性。
設(shè)計(jì)模式的例子:迭代器模式
二、設(shè)計(jì)一個(gè)不能被拷貝的類
拷貝一共就只有兩個(gè)場景,一個(gè)是拷貝構(gòu)造,一個(gè)是賦值運(yùn)算符重載。所以我們想要設(shè)計(jì)出一個(gè)不能被拷貝的類只需要讓外部無法調(diào)用這兩個(gè)函數(shù)即可。
在C++98中,我們的方法是將拷貝構(gòu)造和賦值運(yùn)算符重載只聲明不定義并且將權(quán)限設(shè)置為私有。
class anti_copy
{
public:
anti_copy()
{}
private:
anti_copy(const anti_copy& ac);
anti_copy& operator=(const anti_copy& ac);
};
設(shè)計(jì)原因:
1?? 私有:如果聲明成共有,那么就可以在類外面實(shí)現(xiàn)定義。
2?? 只聲明不定義:因?yàn)槿绻宦暶骶幾g器會(huì)默認(rèn)生成這兩個(gè)的默認(rèn)成員函數(shù)。而不定義是因?yàn)樵摵瘮?shù)不會(huì)被調(diào)用,就不用寫了,這樣編譯的時(shí)候就會(huì)出現(xiàn)鏈接錯(cuò)誤。
而在C++11中引入了關(guān)鍵字——delete。
如果在默認(rèn)成員函數(shù)后跟上=delete
,表示讓編譯器刪除掉該默認(rèn)成員函數(shù)。即使權(quán)限是共有也無法調(diào)用已刪除的函數(shù)。
class anti_copy
{
public:
anti_copy()
{}
anti_copy(const anti_copy& ac) = delete;
anti_copy& operator=(const anti_copy& ac) = delete;
private:
};
三、設(shè)計(jì)一個(gè)只能在堆上創(chuàng)建對(duì)象的類
3.1 私有構(gòu)造
首先要把構(gòu)造函數(shù)給私有,不然這個(gè)類就可以在任意位置被創(chuàng)建。而構(gòu)造函數(shù)被私有了以后我們?cè)趺磩?chuàng)建對(duì)象呢?
我們可以在定義一個(gè)成員函數(shù),讓這個(gè)函數(shù)在堆上申請(qǐng)空間,但我們知道必須現(xiàn)有對(duì)象才能調(diào)用成員函數(shù)。所以我們就把這個(gè)函數(shù)設(shè)置成靜態(tài)成員函數(shù)。
class OnlyHeap
{
public:
static OnlyHeap* GetObj()
{
return new OnlyHeap;
}
private:
OnlyHeap()
{}
};
但是這樣也不完全對(duì),如果我們這么寫:
class OnlyHeap
{
public:
static OnlyHeap* GetObj()
{
return new OnlyHeap;
}
private:
OnlyHeap()
{}
};
int main()
{
OnlyHeap* hp1 = OnlyHeap::GetObj();
OnlyHeap hp2(*hp1);
return 0;
}
這里的hp2就是棧上的對(duì)象。所以我們也要把拷貝構(gòu)造給封住。
class OnlyHeap
{
public:
static OnlyHeap* GetObj()
{
return new OnlyHeap;
}
OnlyHeap(const OnlyHeap& hp) = delete;
private:
OnlyHeap()
{}
};
3.2 私有析構(gòu)
class OnlyHeap
{
public:
OnlyHeap()
{}
OnlyHeap(const OnlyHeap& hp) = delete;
private:
~OnlyHeap()
{}
};
int main()
{
OnlyHeap hp1;// error
OnlyHeap* hp2 = new OnlyHeap;
return 0;
}
這里的hp1就不能創(chuàng)建成功,因?yàn)閷?duì)象銷毀的時(shí)候會(huì)調(diào)用析構(gòu)函數(shù),但是這里的析構(gòu)是私有的,所以該對(duì)象無法調(diào)用。
但是我們要銷毀hp2該怎么辦呢?
我們可以定義一個(gè)成員函數(shù)顯示調(diào)用析構(gòu)函數(shù)。
class OnlyHeap
{
public:
OnlyHeap()
{}
OnlyHeap(const OnlyHeap& hp) = delete;
void Destroy()
{
this->~OnlyHeap();
}
private:
~OnlyHeap()
{}
};
int main()
{
OnlyHeap* hp2 = new OnlyHeap;
hp2->Destroy();
return 0;
}
四、設(shè)計(jì)一個(gè)只能在棧上創(chuàng)建對(duì)象的類
為了不讓這個(gè)類隨便定義出對(duì)象,首先要把構(gòu)造函數(shù)私有。然后跟上面只能在堆上創(chuàng)建對(duì)象的方法相似,定義出一個(gè)靜態(tài)成員函數(shù)返回棧上創(chuàng)建的對(duì)象。
class StackOnly
{
public:
static StackOnly GetObj()
{
return StackOnly();
}
private:
StackOnly()
{}
};
int main()
{
StackOnly hp = StackOnly::GetObj();
return 0;
}
但是這里有一個(gè)問題,無法防止創(chuàng)建靜態(tài)對(duì)象:
static StackOnly hp2 = StackOnly::GetObj();
五、設(shè)計(jì)不能被繼承的類
在C++98,為了不讓子類繼承,我們可以把構(gòu)造函數(shù)私有化,因?yàn)樽宇愋枰日{(diào)用父類的構(gòu)造函數(shù)初始化父類的那一部分成員。
class NoInherit
{
public:
private:
NoInherit()
{}
};
而在C++11中引入的新的關(guān)鍵字final
,被final
關(guān)鍵字修飾的類不能被繼承。
class NoInherit final
{
public:
private:
};
六、單例模式????
一個(gè)類只能創(chuàng)建一個(gè)對(duì)象,即單例模式,該模式可以保證系統(tǒng)中該類只有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn),該實(shí)例被所有程序模塊共享。
單例模式的特點(diǎn)就是全局只有一個(gè)唯一對(duì)象。
6.1 餓漢模式
怎么能做到全局只是用一個(gè)對(duì)象呢,比方說我們現(xiàn)在想要實(shí)現(xiàn)一個(gè)英漢字典,首先我們要把構(gòu)造函數(shù)私有,不然無法阻止創(chuàng)建對(duì)象。然后我們可以在類里面定義一個(gè)自己類型的靜態(tài)成員變量,作用域是全局的。因?yàn)閷?duì)比定義在外邊的靜態(tài)成員變量,內(nèi)部的可以調(diào)用構(gòu)造函數(shù)。
這里要注意把拷貝也要封住。
class Singleton
{
public:
static Singleton& GetObj()
{
return _s;
}
void insert(const std::string& s1, const std::string& s2)
{
_dict[s1] = s2;
}
void Print()
{
for (auto& e : _dict)
{
cout << e.first << "->" << e.second << endl;
}
}
// 防拷貝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton()
{}
std::map<std::string, std::string> _dict;
private:
static Singleton _s;// 聲明
};
Singleton Singleton::_s;// 定義
int main()
{
Singleton::GetObj().insert("corn", "玉米");
Singleton& dic1 = Singleton::GetObj();
dic1.insert("apple", "蘋果");
dic1.insert("banana", "香蕉");
Singleton& dic2 = Singleton::GetObj();
dic2.insert("pear", "梨");
dic2.Print();
return 0;
}
餓漢模式有什么特點(diǎn)呢?
它會(huì)在一開始(main之前)就創(chuàng)建對(duì)象。
餓漢模式有什么缺點(diǎn)呢?
1?? 如果單例對(duì)象構(gòu)造十分耗時(shí)或者占用很多資源,比如加載插件啊, 初始化網(wǎng)絡(luò)連接啊,讀取文件啊等等,而有可能該對(duì)象程序運(yùn)行時(shí)不會(huì)用到,那么也要在程序一開始就進(jìn)行初始化,就會(huì)導(dǎo)致程序啟動(dòng)時(shí)非常的緩慢。
2?? 多個(gè)單例類之間如果有依賴關(guān)系餓漢模式就無法控制,比方說要求A類初始化時(shí)必須調(diào)用B,但是餓漢無法控制先后順序。
所以針對(duì)這些問題,就有了懶漢模式。
6.2 懶漢模式
第一次使用實(shí)例對(duì)象時(shí),創(chuàng)建對(duì)象(用的時(shí)候創(chuàng)建)。進(jìn)程啟動(dòng)無負(fù)載。多個(gè)單例實(shí)例啟動(dòng)順序自由控制。
我們可以直接對(duì)上面餓漢模式的代碼進(jìn)行修改,把靜態(tài)成員變量變成指針。然后把獲取的函數(shù)改變一下:
static Singleton& GetObj()
{
// 第一次調(diào)用才會(huì)創(chuàng)建對(duì)象
if (_s == nullptr)
{
_s = new Singleton;
}
return *_s;
}
整體代碼:
class Singleton
{
public:
static Singleton& GetObj()
{
// 第一次調(diào)用才會(huì)創(chuàng)建對(duì)象
if (_s == nullptr)
{
_s = new Singleton;
}
return *_s;
}
void insert(const std::string& s1, const std::string& s2)
{
_dict[s1] = s2;
}
void Print()
{
for (auto& e : _dict)
{
cout << e.first << "->" << e.second << endl;
}
}
// 防拷貝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton()
{}
std::map<std::string, std::string> _dict;
private:
static Singleton* _s;// 聲明
};
Singleton* Singleton::_s = nullptr;// 定義
6.2.1 線程安全問題
上面的代碼存在問題,當(dāng)多個(gè)線程同時(shí)調(diào)用GetObj(),就會(huì)創(chuàng)建多個(gè)對(duì)象。所以為了線程安全我們要加鎖。為了保證鎖自動(dòng)銷毀,我們可以自定義一個(gè)鎖。文章來源:http://www.zghlxwxcb.cn/news/detail-421828.html
template <class Lock>
class LockAuto
{
public:
LockAuto(Lock& lk)
: _lk(lk)
{
_lk.lock();
}
~LockAuto()
{
_lk.unlock();
}
private:
Lock& _lk;
};
class Singleton
{
public:
static Singleton& GetObj()
{
// 第一次調(diào)用才會(huì)創(chuàng)建對(duì)象
if (_s == nullptr)// 只有第一次才用加鎖
{
LockAuto<mutex> lock(_mutex);
if (_s == nullptr)
{
_s = new Singleton;
}
}
return *_s;
}
void insert(const std::string& s1, const std::string& s2)
{
_dict[s1] = s2;
}
void Print()
{
for (auto& e : _dict)
{
cout << e.first << "->" << e.second << endl;
}
}
// 防拷貝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton()
{}
std::map<std::string, std::string> _dict;
private:
static Singleton* _s;// 聲明
static mutex _mutex;// 鎖
};
Singleton* Singleton::_s = nullptr;// 定義
mutex Singleton::_mutex;// 定義
6.2.2 新寫法
class Singleton
{
public:
static Singleton& GetObj()
{
static Singleton dic;
return dic;
}
void insert(const std::string& s1, const std::string& s2)
{
_dict[s1] = s2;
}
void Print()
{
for (auto& e : _dict)
{
cout << e.first << "->" << e.second << endl;
}
}
// 防拷貝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton()
{}
std::map<std::string, std::string> _dict;
};
這里就用了靜態(tài)局部變量只會(huì)在第一次定義的時(shí)候初始化。在C++11之前是不能保證線程安全的,但是C++11之后就可以了。文章來源地址http://www.zghlxwxcb.cn/news/detail-421828.html
到了這里,關(guān)于【C++】特殊類設(shè)計(jì)(單例模式)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!