C++特殊類設(shè)計(設(shè)計模式)和類型轉(zhuǎn)換方式
在實際應(yīng)用中,會遇到很多常見的特殊類,本篇會介紹這些類的設(shè)計方式
在這之前,雖然沒有聽過設(shè)計模式,但肯定接觸過設(shè)計模式的例子。
比如迭代器模式,是基于面向?qū)ο笕筇匦灾坏?strong>封裝設(shè)計出來的 ,用迭代器類封裝后,不暴露容器結(jié)構(gòu)的情況下,用統(tǒng)一的方式訪問修改容器中的數(shù)據(jù)
再比如適配器模式,運用了復(fù)用的思想,例如 stack 、 queue 等就是根據(jù)底層容器適配而來的
除此之外,還有工廠模式、裝飾器模式、觀察者模式、單例模式等等,由于大部分設(shè)計模式并不常用,因此本篇只會涉及單例模式
不能被拷貝的對象
對于一個對象的拷貝,只會發(fā)生在拷貝構(gòu)造和賦值重載中,那么只需要把這兩個成員方法禁用即可
class CopyBan
{
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(CopyBan) = delete;
public:
//成員方法
private:
//成員變量
};只能在堆上創(chuàng)建的對象
要像該對象只能在堆上被創(chuàng)建,可以將構(gòu)造函數(shù)定義為私有,再用過統(tǒng)一的接口去創(chuàng)建它
class HeapOnly
{
HeapOnly(const HeapOnly&) = delete;//防止拷貝
public:
static HeapOnly* HeapGet()
{
return new HeapOnly;
}
private:
HeapOnly()//構(gòu)造函數(shù)私有化
{
}
};
int main()
{
HeapOnly h1;//報錯
HeapOnly* h2 = HeapOnly::HeapGet();
HeapOnly h3 = *h2;//報錯
return 0;
}只能在棧上創(chuàng)建的對象
既然只能在棧上創(chuàng)建,只需要把統(tǒng)一接口內(nèi)返回的對象改為棧即可
class StackOnly
{
public:
static StackOnly StackGet()
{
return StackOnly();
}
private:
StackOnly()
{
}
};
int main()
{
StackOnly s1 = StackOnly::StackGet();
StackOnly* s2 = new StackOnly;//報錯
return 0;
}還有一種方法是禁用類的專屬operator new,但如果定義static對象時,它是在數(shù)據(jù)段創(chuàng)建的,而不是在棧上,因此有缺陷:
class StackOnly
{
void* operator new(size_t size) = delete;
public:
private:
};
int main()
{
StackOnly s1;
StackOnly* s2 = new StackOnly;//報錯
static StackOnly s3;//不報錯
return 0;
}單例模式
對于某些類,只應(yīng)該有一個實例化對象,這就稱為單例
單例模式有兩種實現(xiàn)方式:
- 餓漢模式:類在加載時就創(chuàng)建實例,例如全局變量就可以理解為餓漢模式(定義時就初始化)
- 懶漢模式:類在第一次使用時再創(chuàng)建實例,一般為調(diào)用getInstance()方法時實例化,它最核心的思想是延時加載,從而優(yōu)化服務(wù)器啟動速度
餓漢舉例:
將構(gòu)造和析構(gòu)函數(shù)私有化,防止外部調(diào)用,并禁用拷貝構(gòu)造和賦值重載,就可以防止用戶構(gòu)造對象
//餓漢
template<class T>
class singletonHungry
{
private:
singletonHungry()
{
cout << "將構(gòu)造函數(shù)私有化\n";
}
~singletonHungry()
{
cout <<"將析構(gòu)函數(shù)私有化\n";
}
//防止使用拷貝構(gòu)造,operator=
singletonHungry(const singletonHungry&) = delete;
void operator=(const singletonHungry&) = delete;
public:
static singletonHungry<T>* getInstance()//static表示該方法屬于類本身,不屬于具體對象
{
return &data;
}
private:
static singletonHungry<T> data;//唯一對象
};
template<class T>
singletonHungry<T> singletonHungry<T>::data;//靜態(tài)成員需再類外聲明
int main()
{
singletonHungry<int>* t = singletonHungry<int>::getInstance();
return 0;
}懶漢舉例:
在函數(shù)內(nèi)定義局部靜態(tài)變量,該變量的生命周期也是隨整個程序,后續(xù)如果再次執(zhí)行定義局部變量的代碼,也會因為已經(jīng)定義過而直接跳過。在多線程中可能存在多個線程同時獲取懶漢單例的情況,因此需要注意線程安全!
//懶漢
template<class T>
class singletonLazy
{
private:
singletonLazy()
{
cout << "將構(gòu)造函數(shù)私有化\n";
}
~singletonLazy()
{
cout <<"將析構(gòu)函數(shù)私有化\n";
}
//防止使用拷貝構(gòu)造,operator=
singletonLazy(const singletonLazy&) = delete;
void operator=(const singletonLazy&) = delete;
public:
static singletonLazy<T>& getInstance()//static表示該方法屬于類本身,不屬于具體對象
{
static singletonLazy<T> data;//C++11起,局部靜態(tài)變量初始化是線程安全的
return data;
}
private:
//成員
};
int main()
{
singletonLazy<int>& sl = singletonLazy<int>::getInstance();
return 0;
}餓漢和懶漢模式的區(qū)別:
- 懶漢模式需要考慮線程安全問題,實現(xiàn)相對復(fù)雜;而餓漢模式不需要
- 懶漢模式是一種懶加載,需要時才會初始化創(chuàng)建對象,不會影響程序的啟動;餓漢模式在程序啟動階段就初始化創(chuàng)建對象,會導(dǎo)致程序啟動慢
- 如果有多個單例類,有B依賴于A的依賴關(guān)系,就必須要先創(chuàng)建A,才能創(chuàng)建B,此時不能用餓漢,因為餓漢不能確定創(chuàng)建的順序,需要用懶漢模式手動控制
- 若單例類中使用了線程或動態(tài)庫,由于餓漢模式時類是在main函數(shù)之前創(chuàng)建,此時不能創(chuàng)建線程,并且用不了動態(tài)庫,只能用懶漢模式
C++的類型轉(zhuǎn)換
static_cast/reinterpret_cast
在C語言階段,類型轉(zhuǎn)換分為顯式/隱式類型轉(zhuǎn)換兩種,隱式類型轉(zhuǎn)換作用于意義相近的類型,顯式類型轉(zhuǎn)換(強制類型轉(zhuǎn)換)作用于意義差別大的類型
int i = 1; double d = 1.5; i = d;//隱式類型轉(zhuǎn)換 int* pi = nullptr; pi = i;//報錯 pi = (int*)i;//顯式類型轉(zhuǎn)換
C++不僅兼容C階段的顯式/隱式類型轉(zhuǎn)換,還有自己的一套轉(zhuǎn)換規(guī)范:
- static_cast<>() 對應(yīng)C語言的隱式類型轉(zhuǎn)換
- reinterpret_cast<>() 對應(yīng)C語言的顯式(強制)類型轉(zhuǎn)換
在轉(zhuǎn)換時,<>中是要轉(zhuǎn)換為的類型,()中是要轉(zhuǎn)換的數(shù)據(jù)
int i = 1; double d = 1.5; i = static_cast<int>(d);//隱式類型轉(zhuǎn)換 int* pi = nullptr; pi = i;//報錯 pi = reinterpret_cast<int*>(i);//顯式類型轉(zhuǎn)換
C++的規(guī)范可以讓可讀性更強
const_cast
const_cast<>() 用于將const變量在轉(zhuǎn)換時去除const屬性(在C語言中就直接用強轉(zhuǎn))
const int ci = 0; int* pi1 = const_cast<int*>(&ci);//C++ int* pi2 = (int*)&ci;//C語言階段
但不管是C語言階段還是C++階段,去除const屬性后的變量,再改值也不會應(yīng)用到原變量中
const int ci = 0; int* pi1 = const_cast<int*>(&ci);//C++ int* pi2 = (int*)&ci;//C語言階段 *pi1 = *pi2 = 5; cout << *pi1 << ' ' << *pi2 << ' ' << '\n' << ci;
5 5 0
這是因為讀取帶const屬性的變量時默認在CPU的寄存器中讀取,即使內(nèi)存中的值改變了,但由于讀取時沒有訪問內(nèi)存,直接從寄存器中拿,因此值沒有變,這是編譯器的優(yōu)化機制。
要想禁用這項編譯器優(yōu)化,可以在定義const變量前加上 volatile 關(guān)鍵字
volatile const int ci = 0;
5 5 5
dynamic_cast
對于繼承類中有虛函數(shù)的類,也就是多態(tài)類,若一個函數(shù)的參數(shù)是父類對象的引用/指針,父子類都可以傳參,若是子類,會發(fā)生切片,這個過程是語法天然支持的(向上轉(zhuǎn)換);若函數(shù)參數(shù)是子類指針/引用,若實參為父類,成功與否要看具體情況(向上轉(zhuǎn)換)
class Parent
{
public:
virtual void fun()
{
}
int _p;
};
class Child : public Parent
{
public:
int _c;
};
void funcast(Parent* p)
{
Child* c = (Child*)p;
c->_c = 1;
c->_p = 2;
cout << c->_c << ' ' << c->_p << '\n';
}
int main()
{
Parent p;
Child c;
funcast(&p);//異常
funcast(&c);//正常運行
return 0;
}dynamic_cast<>() 用于多態(tài)間的類型轉(zhuǎn)換,它可以取代上面強制類型轉(zhuǎn)換的過程,并且指針轉(zhuǎn)換失敗時返回nullptr,引用轉(zhuǎn)換失敗時報異常
void funcast(Parent* p)
{
Child* c = dynamic_cast<Child*>(p);
if(c != nullptr)
{
c->_c = 1;
c->_p = 2;
cout << c->_c << ' ' << c->_p << '\n';
}
}
int main()
{
Parent p;
Child c;
funcast(&p);//不會輸出,但不報錯
funcast(&c);//正常運行
return 0;
}總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
深入分析C++中兩個大數(shù)相乘結(jié)果不正確的問題
本篇文章是對C++中兩個大數(shù)相乘結(jié)果不正確的問題進行了詳細的分析介紹,需要的朋友參考下2013-05-05
C語言 數(shù)據(jù)結(jié)構(gòu)之連續(xù)存儲數(shù)組的算法
這篇文章主要介紹了C語言 數(shù)據(jù)結(jié)構(gòu)之連續(xù)存儲數(shù)組的算法的相關(guān)資料,需要的朋友可以參考下2017-01-01
Protocol Buffer技術(shù)深入理解(C++實例)
C++實例Protocol Buffer技術(shù)詳解,感興趣的朋友可以了解下2013-01-01
解析內(nèi)存對齊 Data alignment: Straighten up and fly right的詳解
對于所有直接操作內(nèi)存的程序員來說,數(shù)據(jù)對齊都是很重要的問題.數(shù)據(jù)對齊對你的程序的表現(xiàn)甚至能否正常運行都會產(chǎn)生影響2013-05-05

