C++智能指針的補充和特殊類的設計示例詳解
定制刪除器:
補充上節(jié)關于未講到的智能指針的一些實踐中經常出現的問題
int main()
{
txf::shared_ptr<A> sp1(new A(1));
return 0;
}這樣寫是沒問題的,他會構造和析構,但如果這樣寫呢?
int main()
{
txf::shared_ptr<A> sp1(new A(1));
txf::shared_ptr<A> sp2(new A[10]);
return 0;
}- DeleteArray<A>() : 匿名對象
由于這里的虛構函數是寫死的 ,這就會導致,這里有九個對象,還沒有釋放
甚至如果說這里是malloc
int main()
{
txf::shared_ptr<A> sp1(new A(1));
txf::shared_ptr<A> sp2(new A[10]);// DeleteArray<A>()匿名對象
txf::shared_ptr<A> sp3((A*)malloc(sizeof(A)));
return 0;
}- 我們之前就說過malloc匹配free, new要匹配delete,這就明顯是不匹配的,不匹配就會出問題
- 那庫里面是怎么解決這個問題的呢?->
- 這個問題也叫做定制刪除器 (依靠仿函數來解決)
template<class D>
shared_ptr(T* ptr,D del)
:_ptr(ptr)
,_pcount(new int(1))
{
}- 這是庫里解決的方法,在構造函數加了一個,模板參數D,他要傳D類型的仿函數給他,那我們肯定也要接收這個仿函數,用誰接收呢?那就想到在類里面要定義一個仿函數來接收
template<class D>
shared_ptr(T* ptr,D del)
:_ptr(ptr) ,_pcount(new int(1)),_del(del)
{
}private: T* _ptr; int* _pcount; D _del;
- 直接增加一個D類型的仿函數,這個D類型能在這里用嗎?
- D是構造函數的模板參數,不是類模板的模板參數,不能這么寫;我們要在析構函數用到del,是del在構造函數怎么辦?我們可以用它來創(chuàng)建一個成員變量,但是他類型是什么?因為D這個是專門在構造函數,才能用的,不是構造函數,不能寫D類型,不能明確他的類型,那我們怎樣創(chuàng)建一個成員中間?如果說del成員專門寫成仿函數類型,那如果傳的是函數指針或者lambda怎么辦呢?這里我們就要用前面講的包裝器,包裝器可以接收他們仨個類型中的任意一個類型
private: T* _ptr; int* _pcount; //D _del //D是構造函數的模板參數,不是類模板的模板參數,不能這么寫; //我們要在析構函數用到del,但是del在構造函數怎么辦? // 我們可以用它來創(chuàng)建一個成員變量,但是他類型是什么? // 因為D這個是專門在構造函數,才能用的,不是構造函數,不能寫D類型, // 不能明確他的類型,那我們怎樣創(chuàng)建一個成員中間? // 如果說del成員專門寫成仿函數類型,那如果傳的是函數指針或者lambda怎么辦呢? // 這里我們就要用前面講的包裝器,包裝器可以接收他們仨個類型中的任意一個類型 function<void(T*)> _del //void : 被調用函數的返回類型 // T* :被調用函數的形參 //int _pcount 如果是這樣,每個對象的_pcount都是獨立的
- void : 被調用函數的返回類型
- T* :被調用函數的形參
我們寫一個專門處理多個對象的仿函數傳給他,這樣就能在析構函數中用這個仿函數,
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
delete[] ptr;
}
};int main()
{
txf::shared_ptr<A> sp1(new A(1));
//由于這里的虛構函數是寫死的 如果這樣寫呢?
txf::shared_ptr<A> sp2(new A[10], DeleteArray<A>());// DeleteArray<A>()匿名對象
//那這里有九個對象,還沒有釋放
//甚至如果說這里是malloc
txf::shared_ptr<A> sp3((A*)malloc(sizeof(A)), [](A* ptr) {free(ptr); });
//我們之前就說過malloc匹配free, new要匹配delete,這就明顯是不匹配的,不匹配就會出問題
//那庫里面是怎么解決這個問題的呢?->定制刪除器(仿函數)
return 0;
} ~shared_ptr()
{
if (--(*_pcount) == 0)
{
cout << "delete:" << _ptr << endl;
_del(_ptr);
delete _pcount;
}
}
這樣是不是看起來沒問題了?其實不是的,我們看似是解決了多個對象銷毀的問題,但是我們好像又不能處理單個對象銷毀的問題,單個對象銷毀需要的是delete而不是delete[] , 所以我們要給包裝器加個缺省值,
private:
T* _ptr;
int* _pcount;
function<void(T*)> _del = [](T* ptr) {delete ptr; };//要先給一個lambda,要不然這個就專門設計成了處理多個對象的了,反而處理當個對象就不行了,所以還要再寫一個拷貝函數
//void : 被調用函數的返回類型
// T* :被調用函數的形參
};
- 要先給一個lambda,要不然這個就專門設計成了處理多個對象的了,反而處理當個對象就不行了,所以還要再寫一個構造函數,
一個構造是構造單個對象的一個構造函數是處理多個對象的 ,要處理多個對象,我們就要傳反函數給他,這樣兩個構造函數就能構成重載
shared_ptr(T* ptr)
:_ptr(ptr), _pcount(new int(1))
{
}
template<class D>
shared_ptr(T* ptr,D del)
:_ptr(ptr) ,_pcount(new int(1)),_del(del)
{
}
以上就是關于定制刪除器的問題
一、請設計一個類,不能被拷貝
拷貝只會放生在兩個場景中:拷貝構造函數以及賦值運算符重載,因此想要讓一個類禁止拷貝,只需讓該類不能調用拷貝構造函數以及賦值運算符重載即可
- C++98的做法
class A
{
// ...
private:
A(const A&);
A& operator=(const A&);
//...};將拷貝構造函數與賦值運算符重載只聲明不定義,并且將其訪問權限設置為私有即可
- 設置成私有:如果只聲明沒有設置成private,用戶自己如果在類外定義了,就可以不
能禁止拷貝了 - 只聲明不定義:不定義是因為該函數根本不會調用,定義了其實也沒有什么意義,不寫反而還簡單,而且如果定義了就不會防止成員函數內部拷貝了
- C++11的做法
class A
{
// ...
A(const A&)=delete;
A& operator=(const A&)=delete;
//...};- C++11擴展delete的用法,delete除了釋放new申請的資源外,如果在默認成員函數后跟上=delete,表示讓編譯器刪除掉該默認成員函數
二、 請設計一個類,只能在堆上創(chuàng)建對象
設計一個類,只能在堆上創(chuàng)建對象 ->什么意思 : 只能顯式申請堆來創(chuàng)建對象,禁止掉一切除該方法外創(chuàng)建對象的行為如 :int a;
int main()
{
HeapOnly hp1;//創(chuàng)建在棧上
static HeapOnly hp2;//創(chuàng)建在靜態(tài)區(qū)上
HeapOnly* hp3 = new HeapOnly;//創(chuàng)建在堆上
return 0;
}
看到以上的區(qū)別后,那該怎么做呢?
由于在棧上和靜態(tài)區(qū)上創(chuàng)建的對象,他們都是能夠自動調用析構函數去釋放空間的,
而創(chuàng)建在堆上的對象的需要手動的去調用析構函數去釋放空間,
所以我們把能自動釋放的析構函數給禁掉,這樣系統就不允許你在棧上和靜態(tài)區(qū)上創(chuàng)建對象,因為沒有析構函數
這樣只能在堆上創(chuàng)建對象,但是在堆上創(chuàng)建對象,你也需要去釋放對象
而我們把析構函數給私有化之后,我們可以再創(chuàng)建一個公共函數,調用析構函數
私有化限定的是類外面,而不會限定類里面
class HeapOnly
{
public:
void Destroy()
{
cout << " deletec : " << this << endl;
delete this;
}
private:
~HeapOnly()
{
}
};那還有沒有其他方法呢?有的
剛才是把析構函數給禁掉,我們還可以把構造函數給私有化,
我們在那里面把構造函數給私有化,但是,
你可以創(chuàng)建一個函數來創(chuàng)建對象,當然,這是針對于堆上創(chuàng)建對象
這樣你就不能調用構造函數,
你就不能在棧上或者在靜態(tài)區(qū)上創(chuàng)建對象,
只能手動的去調用函數創(chuàng)建對象,
函數里面寫成在堆里面創(chuàng)建對象,
所以這樣寫就只能在堆上創(chuàng)建對象
class HeapOnly
{
public:
void Destroy()
{
cout << " deletec : " << this << endl;
delete this;
}
HeapOnly* CreateObj()
{
}
private:
~HeapOnly()
{
}
HeapOnly()
{
}
};
- static讓 CreateObj() 脫離對象,變成“類名::函數”這種普通全局入口 , 讓它變成靜態(tài)區(qū)的一個函數,變成一個用類名調用函數的形式,聲明類就可以調用,這樣不用非得要一個對象才能調用這個函數
- 但是此時面臨個問題他不像是析構函數,使用已經創(chuàng)建好的對象去調用一個函數,他這里是需要創(chuàng)建一個對象去給一個指針,但是誰來調用這個函數呢?我們并沒有對象,我們還要靠這個函數來創(chuàng)造對象,但是又需要一個對象來調用這個函數這就有點像是先有雞還是先有蛋的問題
static讓 CreateObj() 脫離對象,變成“類名::函數”這種普通全局入口
讓它變成靜態(tài)區(qū)的一個函數,變成一個用類名調用函數的形式,聲明類就可以調用,這樣不用非得要一個對象才能調用這個函數
static HeapOnly* CreateObj()//static讓 CreateObj() 脫離對象,變成“類名::函數”這種普通全局入口
{ //讓它變成靜態(tài)區(qū)的一個函數,變成一個用類名調用函數的形式,聲明類就可以調用,
// 這樣不用非得要一個對象才能調用這個函數
return new HeapOnly;//但是此時面臨個問題
//他不像是析構函數,使用已經創(chuàng)建好的對象去調用一個函數,
// 他這里是需要創(chuàng)建一個對象去給一個指針,但是誰來調用這個函數呢?
// 我們并沒有對象,我們還要靠這個函數來創(chuàng)造對象,
// 但是又需要一個對象來調用這個函數
//這就有點像是先有雞還是先有蛋的問題
}此時我們并沒有??掉他的拷貝構造函數,他依舊能夠拷貝構造一個對象,這個創(chuàng)建的對象在棧上
HeapOnly hp4(*hp3);
所以還要把拷貝構造給封一下
private:
~HeapOnly()
{
}
HeapOnly()
{
}
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly& operator=(const HeapOnly& hp) = delete;三、請設計一個類,只能在棧上創(chuàng)建對象
請設計一個類,只能在棧上創(chuàng)建對象
什么叫“設計一個類只能在棧上創(chuàng)建對象”
允許 A a; 、 A a(args);
絕對禁止 new A 、 new A[10]
把 operator new 全家桶全部封殺(含全局、數組、 placement 等)
把析構函數留在公有,否則棧對象離開作用域無法自動析構
(可選)把構造函數公有,否則連棧實例也建不了。一句話總結 “只能在棧上創(chuàng)建”=把通往堆的所有后門(operator new)全部焊死
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly st;
return st;
}
private:
StackOnly()
{ }
};
int main()
{
StackOnly sp1= StackOnly::CreateObj();
return 0;
}
- ??掉構造函數,只給一個CreateObj函數,讓外界調用它來初始化,
在CreateObj函數里面限制,只從棧上面創(chuàng)造對象,這樣,你怎么樣都不可能從堆上創(chuàng)建對象,
在CreateObj函數內創(chuàng)建一個對象返回它
但是該方法還是存在問題
StackOnly* sp2 = new StackOnly(sp1);
- 這樣拷貝對象建立出來的對象還是在棧上面
new 的時候可以調構造,還可以調拷貝構造
new 表達式” = operator new(拿內存) + 構造函數(初始化),operator new相當于全家桶
我們可以重載一個類的專屬的operator new,這樣他就不會調用全局,而會調用我們重載的operator new ,讓void* operator new(size_t size) = delete; 把通往堆的所有后門(operator new)全部焊死
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly st;
return st;
}
private:
StackOnly()
{ }
void* operator new(size_t size) = delete;//把通往堆的所有后門(operator new)全部焊死
};
int main()
{
StackOnly sp1= StackOnly::CreateObj();
//StackOnly* sp2 = new StackOnly(sp1); //這樣就不能拷貝了
return 0;
}四、設計一個類不能被繼承
- C++98的方式
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};- C++98中構造函數私有化,派生類中調不到基類的構造函數。則無法繼承
- C++11的方法
class A final
{
// ....
};- final關鍵字,final修飾類,表示該類不能被繼承
五、 請設計一個類,只能創(chuàng)建一個對象(單例模式)
5.1餓漢模式
單例模式:一個類只能創(chuàng)建一個對象,即單例模式(用這個類定義出來的對象,不管名字是否相同,都是同一個對象) ,該模式可以保證系統中該類只有一個實例,并提供一個
訪問它的全局訪問點,該實例被所有程序模塊共享。比如在某個服務器程序中,該服務器的配置
信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息,這種方式簡化了在復雜環(huán)境下的配置管理
class singleton
{
public:
//步驟二
//提供獲取單例對象的接口函數
static singleton& GetInstance()
{
return _sinst;
}
// singleton sinst;
//一般單例不用釋放
void Add(const pair<string, string>& kv)
{
_dict[kv.first] = kv.second;
}
private:
//單例模式步驟一
//構造函數私有
map<string, string> _dict;
singleton()
{
}
//步驟三
//防拷貝
singleton (const singleton& s) = delete;
singleton& operator=(const singleton& s) = delete;
static singleton _sinst;//聲明一個成員變量
};
//類的靜態(tài)成員變量需要在類外初始化
singleton singleton:: _sinst;//實例化變量
//雖然存儲在靜態(tài)區(qū),但由于在singleton類中聲明了,所以它依然能享用到構造函數,而且在靜態(tài)區(qū),所以只用聲明在哪個類域
int main()
{
//singleton s1;//不能隨意創(chuàng)建對象
//singleton s2;
//if (&s1 == &s2)
//{
// cout << "a" << endl;
//}
//else
//{
// cout << " b";
//}
//cout << &singleton::GetInstance() << endl;
//cout << &singleton::GetInstance() << endl;
//cout << &singleton::GetInstance() << endl;
//還要把拷貝給禁掉,要不然還能拷貝
//singleton copy(singleton::GetInstance());
return 0;
}- 單例模式步驟一 : 構造函數私有
class singleton
{
public:
private:
//單例模式步驟一
//構造函數私有
singleton()
{
}
}:- 步驟二 : 提供獲取單例對象的接口函數
class singleton
{
public:
//步驟二
//提供獲取單例對象的接口函數
static singleton& GetInstance()
{
return _sinst;
}
private:
//單例模式步驟一
//構造函數私有
singleton()
{
}
}:- 首先,我們把構造函數私有化之后,我們要創(chuàng)建一個靜態(tài)成員函數來獲取單例對象,如果不是靜態(tài)的,那么要想調用這個函數,就必須要一個對象,而我們就是要構造出一個對象,所以這就死循環(huán)了,但是如果把它設置為靜態(tài)的,它就存儲在靜態(tài)區(qū),這樣就可以用類::的方式來調用獲取單例對象的函數,而不用非得要創(chuàng)建一個對象,才能調用這個函數,
- 其次,我們正常來說一個類是不可能只實例化出一份對象的,所以我們要想到全局對象或者靜態(tài)對象在作用域內是只有一份的,

- 所以我們要把對象設置為全局或者是靜態(tài)的,由于構造函數是私有的,在類外面是無法創(chuàng)建對象的,所以我們要想辦法讓創(chuàng)建對象可以放到單例類的內部,用于調用構造函數,所以我們要用靜態(tài)成員變量,在單例類的內部,聲明一個靜態(tài)成員對象,他這個對象是純屬在靜態(tài)區(qū)的,是不屬于這個類的,所以在這里不會套娃式的不斷在內部創(chuàng)造,而是只會實例化一次,并且存儲在靜態(tài)區(qū),這樣這個靜態(tài)成員對象就可以訪問構造函數,但是類的靜態(tài)成員變量是需要在類外面初始化的,所以我們就在單例內的外部,初始化定義這個靜態(tài)的單一類對象即可
- 步驟三 : 防拷貝
還要把拷貝給禁掉,要不然還能拷貝
singleton copy(singleton::GetInstance());
完整代碼:
class singleton
{
public:
//步驟二
//提供獲取單例對象的接口函數
static singleton& GetInstance()
{
return _sinst;
}
// singleton sinst;
//一般單例不用釋放
void Add(const pair<string, string>& kv)
{
_dict[kv.first] = kv.second;
}
private:
//單例模式步驟一
//構造函數私有
map<string, string> _dict;
singleton()
{
}
//步驟三
//防拷貝
singleton (const singleton& s) = delete;
singleton& operator=(const singleton& s) = delete;
static singleton _sinst;//聲明一個成員變量
};
//類的靜態(tài)成員變量需要在類外初始化
singleton singleton:: _sinst;//實例化變量
//雖然存儲在靜態(tài)區(qū),但由于在singleton類中聲明了,所以它依然能享用到構造函數,而且在靜態(tài)區(qū),所以只用聲明在哪個類域
以上這種模式也叫做餓漢模式,
就是說不管你將來用不用,程序啟動時就創(chuàng)建一個唯一的實例對象,在一開始(main函數)之前就創(chuàng)建單例對象
不過他也有缺陷,以下是餓漢模式的缺陷 :
- 如果單例對象要初始化的內容很多,那么就會影響進程的啟動速度
- 如果兩個單例類,互相有依賴關系,例如:有兩個單例類A,B。要求A先創(chuàng)建,B再創(chuàng)建,B的初始化依賴于A。倘若單例類A,B不在同一個文件,那么我們無法保證編譯器會去先執(zhí)行哪一個文件,那么就無法保證A始終是先被創(chuàng)建的,那么B的初始化工作可能就無法運行,這就很坑
下面介紹另一種模式,專門解決餓漢模式的缺陷 : 懶漢模式
5.2懶漢模式
懶漢模式:用來解決餓漢模式的問題 (_sinst 改成指針,這樣就不用先創(chuàng)建對象導致啟動慢,等到要調用對象的時候,再創(chuàng)建對象
懶漢模式中的靜態(tài)成員變量,不是一個單例對象 ,而是一個單例對象的指針,我們可以將這個單例對象的指針初始化為nullptr,這樣也就不會調用構造函數了,等到真正使用單例對象的時候,判斷單例對象的指針是否為空,如果單例對象的指針為空,那么就說明此時單例對象還未調用構造函數創(chuàng)建,此時我們new一個單例對象給單例對象的指針即可,這樣就調用了單例類的構造函數進行的初始化單例對象,因為一開始僅僅創(chuàng)建一個空指針沒有什么消耗,所以不會影響進程的啟動速度, 當第一次真正需要使用單例對象的時候才會進行new調用構造函數實例化
namespace lazy
{
class Singleton
{
public:
static Singleton& GetInstace()
{
if (_sinst == nullptr)
{
_sinst = new Singleton();
}
return *_sinst;
}
private:
Singleton()
{}
Singleton(const Singleton& _sinst) = delete;
Singleton& operator=(const Singleton& _sinst) = delete;
static Singleton* _sinst;
};
Singleton* Singleton::_sinst = nullptr;
}
總結
以上就是今天要講的內容,本文僅僅簡單介紹了幾個特殊類,還有相關的單例模式,說實話在寫的時候,小編并未完全弄清楚單例模式以及相關的優(yōu)化場景,所以就只是簡單的介紹了一下,小編要下去再好好弄清楚,弄清楚后再來補充
到此這篇關于C++智能指針的補充和特殊類的設計的文章就介紹到這了,更多相關C++智能指針和特殊類內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++ using namespace std 用法深入解析
以下是對C++中using namespace std的用法進行了詳細的分析介紹,需要的朋友可以過來參考下2013-07-07
Objective-C中使用STL標準庫Queue隊列的方法詳解
這篇文章主要介紹了Objective-C中使用STL標準庫Queue隊列的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-01-01
深入HRESULT與Windows Error Codes的區(qū)別詳解
本篇文章是對HRESULT與Windows Error Codes的區(qū)別進行了詳細的分析介紹,需要的朋友參考下2013-05-05

