天天看點

c/c++開發,無可避免的模闆程式設計實踐(篇一)

一、c++模闆

        c++開發中,在聲明變量、函數、類時,c++都會要求使用指定的類型。在實際項目過程中,會發現很多代碼除了類型不同之外,其他代碼看起來都是相同的,為了實作這些相同功能,我們可能會進行如下設計:

int max(const int &v1, const int &v2){
    return (v1>v2)?v1:v2;
};
double max(const double &v1, const double &v2){
    return (v1>v2)?v1:v2;
};
string max(const string &v1, const string &v2){
    return (v1>v2)?v1:v2;
};
...
           

        這明明針對相同行為的不同類型比較而已,卻一次又一次去重複實作它,做了如此多重複的工作,甚至還可能一不留神就引入更多錯誤,開始一輪痛苦的調整之旅。而借助C++的模闆方法,就可以更好地優化此類代碼,更好地利用c++類型檢查的特點,更好格式的源代碼。

        目前模闆的應用在c++中非常廣泛,如,在c++标準庫中,幾乎所有代碼都是模闆代碼,很多第三方c++庫也或多或少都會使用模闆。模闆程式設計是c++開發中無可避免的一道坎。

        模闆是為多種類型而編寫的函數和類,這些類型都沒有指定,等在使用模闆時,再把類型作為一個(顯式或隐式的)實參傳遞給模闆,完成具體場景化的應用。

二、函數模闆

       2.1 函數模闆定義

         函數模闆提供類一種函數行為,其可以用多種不同的類型進行調用,一個函數模闆就代表一個函數家族。前面提到比較兩個資料的大小并傳回較大值,通過模闆可以簡化:

//template <class T>	//class 和 typename 沒有差別
template <typename T>
T max(const T &v1, const T &v2)
{
	return (v1>v2)?v1:v2;
};
           

        模闆定義以關鍵字 template 開始,後接模闆形參表,模闆形參表是用尖括号包覆的一個或多個模闆形參的清單,形參之間以逗号分隔,模闆形參表不能為空。這個模闆定義指定一個“傳回兩個值中最大值”的家族函數,參數類型還沒确定,采用模闆參數T來替代,參數清單是typename T。注意,模闆形參作用域和非模闆形參的作用域是類似的,這裡就不展開闡述了。

        在這裡class T和 typename T是等價的,鑒于曆史原因,可能會使用class取代typename,來定義類型參數。在c++語言發展演化過程中,關鍵字typename 的出現相對較晚一些,在它之前都是關鍵字class作為模闆的類型參數引入的,c++也保留了下來。若要支援一些舊版本編譯工具、庫等就采用class,否則建議使用關鍵字typename。但需要注意struct關鍵字是不行的,不要因為class可行就産生誤導了。

        像函數形參一樣,程式員為模闆形參選擇的名字沒有本質含義。将 max的模闆類型形參命名為 T,但也可以将它命名為任意名字:

template <typename this_is_long_name>
this_is_long_name max(const this_is_long_name &v1, const this_is_long_name &v2)
{
	return (v1>v2)?v1:v2;
};
           

        2.2 模闆調用

        在使用模闆時,編譯器會根據傳入實參進行檢測,編譯器會推斷哪個(或哪些)模闆實參綁定到模闆形參。一旦編譯器确定了實際的模闆實參,就稱它執行個體化了函數模闆的一個執行個體。實質上,編譯器将确定用什麼類型代替每個類型形參,以及用什麼值代替每個非類型形參。推導出實際模闆實參後,編譯器使用實參代替相應的模闆形參産生編譯該版本的函數。

max<int>(14,16);	
//編譯器實質類似于
int max_int(const int &v1, const int &v2)
{
	return (v1>v2)?v1:v2;
};
 
max_int(14,16);	
           

        上述代碼展示,使用了int作為模闆參數T的函數模闆,就會用具體類型替代模闆參數,這個過程就叫執行個體化,它會産生一個模闆執行個體。隻要使用了函數模闆,編譯器就會自動觸發這樣的一個執行個體化過程,是以開發者并不需要額外地請求模闆的執行個體化。

        在使用函數模闆時,通過具體參數類型傳遞實參,編譯器就會明确參數類型,其不允許自動類型轉換,每個模闆參數都必須正确地比對。

std::cout << max(14,16) << std::endl;		//OK
	std::cout << max<int>(14,16) << std::endl;	//OK,建議
	std::cout << max<std::string>(("nihao"),("hi")) << std::endl;	//顯式指定
	//std::cout << max("nihao","hi") << std::endl;					//error,編譯器無法明确
	//std::cout << max(14,16.5) << std::endl;					    //error,類型不比對
	std::cout << max<float>(14,16.5) << std::endl;                  //OK,顯式指定
    std::cout << max(static_cast<double>(14),16.5) << std::endl;     //OK,強制類型轉換
           

        2.3 非類型模闆形參

        模闆形參不必都是類型,函數模闆也可以使用的非類型形參。

template <typename T, int LEN>
void print(const T (&val)[LEN])
{
	for(int i=0; i<LEN; i++)
    {
		std::cout << val[i] << std::endl;
    }
};

//
std::string str[2] = {"hello","world"};
print(str);				//OK 
print<std::string>(str);//OK
           

        2.4 模闆聲明和定義源碼分離

        模闆的聲明和定義一般都建議放置同一頭檔案内,将聲明和定義分離到頭檔案及源檔案内,c++編譯器會接受,但連接配接器會出錯。

/*test_template.h*/
#ifndef _TEST_TEMPLATE_H_
#define _TEST_TEMPLATE_H_

//
template <typename T> 
T min(const T &v1, const T &v2);

#endif //_TEST_TEMPLATE_H_


/*test_template.cpp*/
#include "test_template.h"

template <typename T>
T min(const T &v1, const T &v2)
{
	return (v1<v2)?v1:v2;
};

/*test.cpp*/
#include "test_template.h"
int main(int argc, char* argv[])
{
	std::cout << min(14,16) << std::endl;		
	return 0;
};
           

        編譯時,連接配接器會出錯,

ccZMcsD7.o:test.cpp:(.text+0x207): undefined reference to `int min<int>(int const&, int const&)'
collect2.exe: error: ld returned 1 exit status
           

       當然您也可以在頭檔案裡包含源檔案,等同于模闆的聲明和定義一般都建議放置同一頭檔案内。

/*test_template.h*/
#ifndef _TEST_TEMPLATE_H_
#define _TEST_TEMPLATE_H_

//
template <typename T> 
T min(const T &v1, const T &v2);

#include "test_template.cpp"    //僅這裡不同了哦-------------------
#endif //_TEST_TEMPLATE_H_
/*---------------------------------------------------*
/*test_template.cpp*/
#include "test_template.h"

template <typename T>
T min(const T &v1, const T &v2)
{
	return (v1<v2)?v1:v2;
};
/*---------------------------------------------------*
/*test.cpp*/
#include "test_template.h"
int main(int argc, char* argv[])
{
	std::cout << min(14,16) << std::endl;		
	return 0;
};
           

         是以建議是模闆的聲明和定義都放置同一頭檔案内。

#ifndef _TEST_TEMPLATE_H_
#define _TEST_TEMPLATE_H_

//
template <typename T> 
T min(const T &v1, const T &v2);
{
	return (v1<v2)?v1:v2;
};
#endif //_TEST_TEMPLATE_H_
           

        如果非要進行分離,也是有辦法就是較麻煩。另建立一個頭檔案放置模闆函數的定義,為了差別,起名.hpp,然後再建立一個頭檔案,對模闆函數進行顯式執行個體化,就可以避免連結期報錯。

/*test_template.h*/
#ifndef _TEST_TEMPLATE_H_
#define _TEST_TEMPLATE_H_
//
template <typename T> 
T min(const T &v1, const T &v2);

#endif //_TEST_TEMPLATE_H_

/*test_template.hpp*/
#ifndef _TEST_TEMPLATE_HPP_
#define _TEST_TEMPLATE_HPP_

#include "test_template.h"

template <typename T>
T min(const T &v1, const T &v2)
{
	return (v1<v2)?v1:v2;
};

#endif //_TEST_TEMPLATE_HPP_

/*test_template.cpp*/
#include "test_template.hpp"
template int min<int>(const int &v1, const int &v2);
template double min<double >(const double &v1, const double &v2);

/*test.cpp*/
#include "test_template.h"
int main(int argc, char* argv[])
{
	std::cout << min(14,16) << std::endl;	//OK	
	return 0;
};
           

        再編譯時就可以通過。

c/c++開發,無可避免的模闆程式設計實踐(篇一)

         另外如果您手頭上的c++編譯器是支援export這個關鍵字特性的話,做模闆聲明和定義分離編寫會更容易。

/*test_template.h*/
#ifndef _TEST_TEMPLATE_H_
#define _TEST_TEMPLATE_H_
//
export template <typename T> 
T min(const T &v1, const T &v2);

#endif //_TEST_TEMPLATE_H_

/*test_template.cpp*/
#include "test_template.h"

export template <typename T>
T min(const T &v1, const T &v2)
{
	return (v1<v2)?v1:v2;
};


/*test.cpp*/
#include "test_template.h"
int main(int argc, char* argv[])
{
	std::cout << min(14,16) << std::endl;	//OK	
	return 0;
};
           

        但是,關于export這個關鍵字特性,目前市面上支援的C++編譯器是較少的,是以采用export時,可能就通過宏定義編譯條件了集合上述兩種方法才能做到代碼在各個編譯器平滑支援了。

        2.5 函數模闆支援inline

        另外函數模闆可以用與非模闆函數一樣的方式聲明為 inline。inline說明符放在模闆形參表之後、傳回類型之前,不能放在關鍵字 template 之前。

template <typename T> inline T test(const T &v1){return v1;}; //OK
inline template <typename T> T test(const T &v1){return v1;}; //error
           

        2.6 函數模闆重載

        和普通函數一樣,函數模闆也可以被重載,相同模闆函數名稱可具有不同函數定義,當使用函數名稱進行模闆函數調用時,編譯器會根據調用語句進行檢查推斷調用比對度更高的哪個候選模闆函數。

template <typename T>
inline T max(const T &v1, const T &v2)
{
	return (v1>v2)?v1:v2;
};

template <typename T>
T max(const T &v1, const T &v2, const T &v3)
{
	return max(max(v1,v2),v3);
};

//
std::cout << max(14,16,15) << std::endl;		//OK
           

        但需要注意的是,模闆函數和普通函數重載時,參數個數不一緻是不行的

template <typename T>
inline T max(const T &v1, const T &v2)
{
	return (v1>v2)?v1:v2;
};

int max(const int &v1){	return 0;}    //編譯錯誤,multiple definition of `max'
           

        但如果普通函數看着和模闆函數函數名相同,參數格式一緻,那就是模闆函數的特例化實作。

template <typename T>
inline T max(const T &v1, const T &v2)
{
	return (v1>v2)?v1:v2;
};

inline char max(const char &v1, const char &v2)
{
	return (v1>v2)?v1:v2;
};

std::cout << max('a','c') << std::endl;	//OK,大家認為會調用哪個函數呢,如果第二個函數在前呢
std::cout << max<char>('a','b') << std::endl;	
           

        普通函數(非模闆函數)和模闆函數同名及參數格式一緻時,該模闆函數可以被執行個體化為這個非模闆函數,在調用時,編譯器會根據重載解析過程調用比對度更高的函數。在看看下面案例:

template <typename T>
inline T max(const T &v1, const T &v2)
{
	return (v1>v2)?v1:v2;
};

template <typename T>
T max(const T &v1, const T &v2, const T &v3)
{
	return max(max(v1,v2),v3);    //使用模闆函數,int的函數聲明太遲了
};

inline char max(const char &v1, const char &v2)
{
	return (v1>v2)?v1:v2;
};

std::cout << max('a','b','c') << std::endl;		//OK,這個呢,會調用那個函數(兩個參數的max)
           

三、類模闆

        3.1 類模闆定義

        和函數一樣,類也可以被定義為模闆,像标準庫中的容器就類模闆,它被用于管理某種特定類型的元素,在使用它們時,不需要确定容器中元素的類型。下面以本人曾定義過的一個類模闆為例來唠叨一下類模闆:

/*queuedata.h*/
template <class T>
class QueueData
{
public:
	QueueData(std::string desc = "thread_queue");
	~QueueData();
	//
	/**
	 * 擷取隊列大小
	 * @return {int } 隊列大小
	 */
	int size();
	/**
	 * 判定隊列是否為空
	 * @return {bool } 是否為空隊列
	 */
	bool isEmpty();
	/**
	 * 擷取隊列頭元素
	 * @param it {T&} 頭元素
	 * @return {bool } 是否成功
	 */
	bool getFirst(T &it);
	/**
	 * 删除元素
	 * @return {bool } 是否成功
	 */
	bool removeFirst();
	/**
	 * 擷取隊列頭元素,并從隊列終删除
	 * @param it {T&} 頭元素
	 * @return {bool } 是否成功
	 */
	bool pop(T &it);
	/**
	 * 從隊列頭開始逐漸擷取多個元素,并剔除
	 * @param its {queue<T>&} 擷取到的元素集
	 * @param sizel {int} 一次擷取多少個
	 * @return {bool } 至少擷取一個元素以上則成功
	 */
	bool getList(std::queue<T> &its,unsigned int sizel=5);
	/**
	 * 從隊列尾部添加元素
	 * @param it {T} 被添加元素
	 * @return {void } 無傳回
	 */
	void add(T it);
	/**
	 * 從隊列頭部添加元素
	 * @param it {T} 被添加元素
	 * @return {void } 無傳回
	 */
	void add_front(T it);
	/**
	 * 清空元素
	 * @return {void }
	 */
	void clear();
private:
	void init();
	QueueData& operator=(const QueueData&) {return this;};
protected:
	std::string queue_desc;
private:
	/點集轉發
	
	//協定解析結果緩存
	std::deque<T> datacache_queue;	//隊列容器
	PYMutex m_Mutex;				//線程鎖,或者如果更徹底采用acl庫,采用acl::thread_mutex替代
	//
	static unsigned int QSize;		//隊列大小限制,超出是會從隊列頭剔除舊資料騰出空位在對末添加資料
	//
	int queue_overS;				//隊列溢出次數計數
};
           

        類模闆也是模闆,是以必須以關鍵字 template 開頭,後接模闆形參表。QueueData模闆接受一個名為 T 的模闆類型形參。

        除了模闆形參表外,類模闆的定義看起來與任意其他類問相似。 類模闆可以定義資料成員、函數成員和類型成員,也可以使用通路标号控制對成員的通路,還可以定義構造函數和析構函數等等。在類和類成員的定義中,可以使用模闆形參作為類型或值的占位符,在使用類時再提供那些類型或值。

        例如,上述QueueData模闆有一個模闆類型形參,可以在任何可以使用類型名字的地方使用該形參。在這個模闆定義中,用 T作為 add操作的形參類型。QueueData模闆是基于标準庫中的std::deque<>來實作的,就不需要親自實作記憶體管理、拷貝構造、指派及算法等,把精力聚焦在業務邏輯開發上。

        和函數模闆調用對比,使用類模闆時,必須為模闆形參顯式指定實參。編譯器使用實參來執行個體化這個類的特定類型版本,QueueData 不是類型,而 QueueData <int>或 QueueData <string> 是類型。實質上,編譯器用使用者提供的實際特定類型代替 T,重新編寫 QueueData類。

#include "queuedata.h"

QueueData<int> data1;	//OK
//QueueData data2;		//error
QueueData<std::string> data3;	//OK
           

        3.2 類模闆的成員變量及成員函數

        類模闆的成員函數定義和函數模闆分離時定義類似

//queuedata.h
template <class T>
void QueueData<T>::init() 
{
	queue_overS = 0;
};
           

        采用模闆參數T定義的成員變量也和普通類成員變量用法一緻

template <class T>
class QueueData{
// 省略其他
private:
    std::deque<T> datacache_queue;	//隊列容器
};

template <class T>
void QueueData<T>::add(T it) 
{
	m_Mutex.Lock();
	if (datacache_queue.size() > QSize) 
	{
		queue_overS++;
		datacache_queue.pop_front();
	}
	datacache_queue.push_back(it);
	m_Mutex.Unlock();
	if (queue_overS >= 10) 
	{
		//每溢出10次,報告一次
		printf("add item to queue %s at end,but the size of QueueData is up to limmit size: %d.\n"
			, queue_desc.c_str(), QSize);
		queue_overS = 0;
	}
}
           

        對于static靜态成員,用法也一緻,無非就是帶上模闆定義而已:

template <class T>
class QueueData{
// 省略其他
private:
    static unsigned int QSize;//隊列大小限制,超出是會從隊列頭剔除舊資料騰出空位在對末添加資料
};

//
template <class T>
unsigned int  QueueData<T>::QSize = 100;
           

        3.3 類模闆的特例化

        類模闆可以通過指定實參來特例化,和函數模闆的重載類似,通過特化類模闆,通常是用來可以克服某種特定類型在執行個體化類模闆時所出現的不足。

template <>
class QueueData<std::string>
{
    ...
};
//相應地,類模闆特例化時,每個成員函數必須都重新定義為普通函數,模闆參數T也被特例化類型取代。
int QueueData<std::string>::size()
{
	int ret = 0;
	m_Mutex.Lock();
	ret = static_cast<int>(datacache_queue.size());
	m_Mutex.Unlock();
	return ret;
}
           

        當類模闆有多個模闆參數時,也可以進行局部特例化

template <typename T1, typename T2>
class TestData
{
    ...
};
//局部特例化,兩個模闆參數相同
template <typename T>
class TestData<T,T>
{
    ...
};

//局部特例化,第2模闆參數int
template <typename T>
class TestData<T,int>
{
    ...
};
//局部特例化,兩個模闆參數改為指針
template <typename T1, typename T2>
class TestData<*T1,*T2>
{
    ...
};
           

       3.4 非類型模闆參數及預設值

         對于類模闆,還可以為模闆參數定義預設值。

template<typename T, typename VALS= std::vector<T> >
class TestData{
    private:
    VALS els;
};
           

        此外,對于模闆,模闆參數并不局限于類型,普通值也可以作為模闆參數,可以給類模闆指定類型及指定預設值,本專欄在講述TCP/Socket通信代碼優化時就用到過:

#define RDC_SIZE 1024	//服務端從socket讀取資料時的緩存大小
#define DATA_SIZE 512	//服務及用戶端的資料傳輸(讀取及寫入)的緩存大小

template<int SIZE = DATA_SIZE>
struct TCP_Data
{
	TCP_Data() 
		: len(0)
	{
		memset(Buf,0,SIZE);
	};
    ...省略其他...
	unsigned char Buf[SIZE];
	int len;
};

//調用
TCP_Data<> a;          //大小512
TCP_Data<DATA_SIZE> a; //大小512
           

        在這個例子裡,我們使用元素數目固定的數組來實作,其優點就是避免記憶體開銷,讓使用者親自制定數組的大小。當然這個例子極端了些,再看看下面這個:

template<typename T, int MAXSIZE>
class TCP_Data
{
public:
    TCP_Data();
    void doit();
private:
    int len;
    T els[MAXSIZE];
};
//定義成員函數
template<typename T, int MAXSIZE>
TCP_Data<T,MAXSIZE>::TCP_Data() 
	: len(0)
{
};
//
template<typename T, int MAXSIZE>
void TCP_Data<T,MAXSIZE>::doit()
{
};

//使用時,同時制定模闆參數類型和最大容量
TCP_Data<int,10> a;
           

        特别注意的是,非類型模闆參數是有限制的,它們常整數(包括枚舉值)或者指向外部連結對象的指針。

template<double VAT> double doit(double val){};    //error
template<std::string name> class MyClass{};        //error

template<char const * name> class MyClass{};        //error
char const* s = "hi";
MyClass<s> a;    //error,指向内部連結對象

extern char const s[] = "hi";
MyClass<s> a;    //OK,指向外部連結對象
           

四、編譯及附錄代碼

c/c++開發,無可避免的模闆程式設計實踐(篇一)

         編譯及運作輸出

c/c++開發,無可避免的模闆程式設計實踐(篇一)

        Makefile:

CX	=	g++

GCC_CMD_DEF := 1
BIN 		:= .
TARGET      := test.exe
#FLAGS		:= -static

Include		:= .
source		:= test.cpp Mutex.cpp test_template.cpp 
#可以注釋掉test_template.cpp 并在test_template.h的26行開啟測試另一種僞分離的效果
$(TARGET) :
	$(CX) $(FLAGS) $(source) -I$(Include) -o $(BIN)/$(TARGET)

clean:
	rm  $(BIN)/$(TARGET)
           

        test_template.h

#ifndef _TEST_TEMPLATE_H_
#define _TEST_TEMPLATE_H_

#include <iostream>
//template <class T>	//class 和 typename 沒有差別
template <typename T>
inline T max(const T &v1, const T &v2)
{
	return (v1>v2)?v1:v2;
};

template <typename T>
T max(const T &v1, const T &v2, const T &v3)
{
	return max(max(v1,v2),v3);
};

inline char max(const char &v1, const char &v2)
{
	return (v1>v2)?v1:v2;
};

//
template <typename T> 
T min(const T &v1, const T &v2);
//#include "test_template.hpp"	開該項,就不需要test_template.cpp咯

template <typename T, int LEN>
void print(const T (&val)[LEN])
{
	for(int i=0; i<LEN; i++)
	{
		std::cout << val[i] << std::endl;
	}
};

template<typename T, int MAXSIZE>
class TCP_Data
{
public:
	TCP_Data();
	~TCP_Data();
	void add(T const & elem);
    void doit();
private:
	int len;
    T els[MAXSIZE];
	
};
//定義成員函數
template<typename T, int MAXSIZE>
TCP_Data<T,MAXSIZE>::TCP_Data() 
	: len(0)
{
	
};

template<typename T, int MAXSIZE>
TCP_Data<T,MAXSIZE>::~TCP_Data()
{
};

template<typename T, int MAXSIZE>
void TCP_Data<T,MAXSIZE>::add(T const & elem)
{
	if(len>=MAXSIZE)
	{
		//
		return;
	}
	els[len] = elem;
	++len;
};
	
template<typename T, int MAXSIZE>
void TCP_Data<T,MAXSIZE>::doit()
{
	for(int i=0; i<len; i++)
	{
		std::cout << els[i] << std::endl;	//若T是自定義類型呢
	}
};

#endif //_TEST_TEMPLATE_H_
           

        test_template.hpp

#ifndef _TEST_TEMPLATE_HPP_
#define _TEST_TEMPLATE_HPP_

#include "test_template.h"

template <typename T>
T min(const T &v1, const T &v2)
{
	return (v1<v2)?v1:v2;
};

#endif //_TEST_TEMPLATE_HPP_
           

        test_template.cpp

#include "test_template.hpp"

template int min<int>(const int &v1, const int &v2);
template double min<double>(const double &v1, const double &v2);
           

        test.cpp

#include <iostream>
#include "test_template.h"
#include "queuedata.h"

int main(int argc, char* argv[])
{
	//模闆調用
	std::cout << max(14,16) << std::endl;		//OK
	std::cout << max<int>(14,16) << std::endl;	//OK,建議
	std::cout << max<std::string>(("nihao"),("hi")) << std::endl;	//顯式指定
	//std::cout << max("nihao","hi") << std::endl;					//error,編譯器無法明确
	//std::cout << max(14,16.5) << std::endl;					    //error,類型不比對
	std::cout << max<float>(14,16.5) << std::endl;                  //OK,顯式指定
    std::cout << max(static_cast<double>(14),16.5) << std::endl;     //OK,強制類型轉換
	//
	std::string str[2] = {"hello","world"};
	print(str);				//OK 
	print<std::string>(str);//OK
	//分離聲明及定義
	std::cout << min(14,16) << std::endl;		//OK
	//重載
	std::cout << max(14,16,15) << std::endl;		//OK
	//重載
	std::cout << max('a','c') << std::endl;			//OK
	std::cout << max<char>('a','b') << std::endl;	//OK
	std::cout << max('a','b','c') << std::endl;		//OK
	//
	QueueData<int> data1;	//OK
	//QueueData data2;		//error
	QueueData<std::string> data3;	//OK
	data3.add("hi");
	data3.add_front("hello");
	std::string s_="0";
	data3.getFirst(s_);
	std::cout <<"first: " << s_ << std::endl;			//OK
	//
	TCP_Data<int,2> vals;
	vals.add(7);
	vals.add(17);
	vals.doit();
	return 0;
};
           

        queuedata.h

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#ifndef _QUEUE_DATA_H_
#define _QUEUE_DATA_H_

/***********************************************************************
  *Copyright 2020-03-06, pyfree
  *
  *File Name       : queuedata.h
  *File Mark       : 
  *Summary         : 
  *資料隊列類,線程安全
  *
  *Current Version : 1.00
  *Author          : pyfree
  *FinishDate      :
  *
  *Replace Version :
  *Author          :
  *FinishDate      :

 ************************************************************************/
#include <queue>
#include <deque>
#include <stdio.h>
#include <string.h>

#include "Mutex.h"

template <class T>
class QueueData
{
public:
	QueueData(std::string desc = "thread_queue");
	~QueueData();
	//
	/**
	 * 擷取隊列大小
	 * @return {int } 隊列大小
	 */
	int size();
	/**
	 * 判定隊列是否為空
	 * @return {bool } 是否為空隊列
	 */
	bool isEmpty();
	/**
	 * 擷取隊列頭元素
	 * @param it {T&} 頭元素
	 * @return {bool } 是否成功
	 */
	bool getFirst(T &it);
	/**
	 * 删除元素
	 * @return {bool } 是否成功
	 */
	bool removeFirst();
	/**
	 * 擷取隊列頭元素,并從隊列終删除
	 * @param it {T&} 頭元素
	 * @return {bool } 是否成功
	 */
	bool pop(T &it);
	/**
	 * 從隊列頭開始逐漸擷取多個元素,并剔除
	 * @param its {queue<T>&} 擷取到的元素集
	 * @param sizel {int} 一次擷取多少個
	 * @return {bool } 至少擷取一個元素以上則成功
	 */
	bool getList(std::queue<T> &its,unsigned int sizel=5);
	/**
	 * 從隊列尾部添加元素
	 * @param it {T} 被添加元素
	 * @return {void } 無傳回
	 */
	void add(T it);
	/**
	 * 從隊列頭部添加元素
	 * @param it {T} 被添加元素
	 * @return {void } 無傳回
	 */
	void add_front(T it);
	/**
	 * 清空元素
	 * @return {void }
	 */
	void clear();
private:
	void init();
	QueueData& operator=(const QueueData&) {return this;};
protected:
	std::string queue_desc;
private:
	/點集轉發
	
	//協定解析結果緩存
	std::deque<T> datacache_queue;	//隊列容器
	PYMutex m_Mutex;				//線程鎖,或者如果更徹底采用acl庫,采用acl::thread_mutex替代
	//
	static unsigned int QSize;		//隊列大小限制,超出是會從隊列頭剔除舊資料騰出空位在對末添加資料
	//
	int queue_overS;				//隊列溢出次數計數
};
template <class T>
unsigned int  QueueData<T>::QSize = 100;

template <class T>
QueueData<T>::QueueData(std::string desc)
	: queue_desc(desc)
{
	init();
};

template <class T>
void QueueData<T>::init() 
{
	queue_overS = 0;
};

template <class T>
QueueData<T>::~QueueData()
{

}

//
template <class T>
int QueueData<T>::size()
{
	int ret = 0;
	m_Mutex.Lock();
	ret = static_cast<int>(datacache_queue.size());
	m_Mutex.Unlock();
	return ret;
}

template <class T>
bool QueueData<T>::isEmpty()
{
	bool ret = false;
	m_Mutex.Lock();
	ret = datacache_queue.empty();
	m_Mutex.Unlock();
	return ret;
}

template <class T>
bool QueueData<T>::getFirst(T &it) 
{
	bool ret = false;
	m_Mutex.Lock();
	if (!datacache_queue.empty()) 
	{
		it = datacache_queue.front();
		ret = true;
	}
	m_Mutex.Unlock();
	return ret;
}

template <class T>
bool QueueData<T>::removeFirst() 
{
	bool ret = false;
	m_Mutex.Lock();
	if (!datacache_queue.empty()) 
	{
		datacache_queue.pop_front();
		ret = true;
	}
	m_Mutex.Unlock();
	return ret;
}

template <class T>
bool QueueData<T>::pop(T &it)
{
	bool ret = false;
	m_Mutex.Lock();
	if (!datacache_queue.empty()) 
	{
		it = datacache_queue.front();
		datacache_queue.pop_front();
		ret = true;
	}
	m_Mutex.Unlock();
	return ret;
};

template <class T>
bool QueueData<T>::getList(std::queue<T> &its,unsigned int sizel)
{
	m_Mutex.Lock();
	while (!datacache_queue.empty())
	{
		its.push(datacache_queue.front());
		datacache_queue.pop_front();
		if (its.size() >= sizel)
		{
			break;
		}
	}
	m_Mutex.Unlock();
	return !its.empty();
};

template <class T>
void QueueData<T>::add(T it) 
{
	m_Mutex.Lock();
	if (datacache_queue.size() > QSize) 
	{
		queue_overS++;
		datacache_queue.pop_front();
	}
	datacache_queue.push_back(it);
	m_Mutex.Unlock();
	if (queue_overS >= 10) 
	{
		//每溢出10次,報告一次
		printf("add item to queue %s at end,but the size of QueueData is up to limmit size: %d.\n"
			, queue_desc.c_str(), QSize);
		queue_overS = 0;
	}
}

template <class T>
void QueueData<T>::add_front(T it)
{
	m_Mutex.Lock();
	if (datacache_queue.size() > QSize) 
	{
		queue_overS++;
		datacache_queue.pop_front();
	}
	datacache_queue.push_front(it);
	m_Mutex.Unlock();
	if (queue_overS >= 10) 
	{
		//每溢出10次,報告一次
		printf("add item to queue %s at first,but the size of QueueData is up to limmit size: %d.\n"
			, queue_desc.c_str(), QSize);
		queue_overS = 0;
	}
}

template <class T>
void QueueData<T>::clear()
{
	m_Mutex.Lock();
	datacache_queue.clear();
	m_Mutex.Unlock();
	queue_overS = 0;
}

#endif //_QUEUE_DATA_H_
           

        Mutex.h

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#ifndef _PYMUTEX_H_
#define _PYMUTEX_H_

/***********************************************************************
  *Copyright 2020-03-06, pyfree
  *
  *File Name       : Mutex.h
  *File Mark       : 
  *Summary         : 線程鎖
  *
  *Current Version : 1.00
  *Author          : pyfree
  *FinishDate      :
  *
  *Replace Version :
  *Author          :
  *FinishDate      :

 ************************************************************************/

#ifdef WIN32
//#include <windows.h>
#else
#include <pthread.h>
#endif

typedef void *HANDLE;

class IMutex
{
public:
	virtual ~IMutex() {}

  /**
	 * 上鎖
	 * @return {void} 
	 */
	virtual void Lock() const = 0;
  /**
	 * 嘗試上鎖
	 * @return {void} 
	 */
	virtual bool TryLock() const = 0;
  /**
	 * 解鎖
	 * @return {void} 
	 */
	virtual void Unlock() const = 0;
};

class PYMutex : public IMutex
{
public:
	PYMutex();
	~PYMutex();

	virtual void Lock() const;
	virtual bool TryLock() const;
	virtual void Unlock() const;
private:
#ifdef _WIN32
	HANDLE m_mutex;
#else
	mutable pthread_mutex_t m_mutex;
#endif
};

#endif //_PYMUTEX_H_
           

        Mutex.cpp

#include "Mutex.h"

#ifdef WIN32
#include <windows.h>
#endif
//#include <iostream>
#include <stdio.h>

PYMutex::PYMutex()
{
#ifdef _WIN32
	m_mutex = ::CreateMutex(NULL, FALSE, NULL);
#else
	pthread_mutex_init(&m_mutex, NULL);
#endif
}


PYMutex::~PYMutex()
{
#ifdef _WIN32
	::CloseHandle(m_mutex);
#else
	pthread_mutex_destroy(&m_mutex);
#endif
}


void PYMutex::Lock() const
{
#ifdef _WIN32
	//DWORD d = WaitForSingleObject(m_mutex, INFINITE);
	WaitForSingleObject(m_mutex, INFINITE);
	/// \todo check 'd' for result
#else
	pthread_mutex_lock(&m_mutex);
#endif
}

bool PYMutex::TryLock() const
{
#ifdef _WIN32
    DWORD dwWaitResult = WaitForSingleObject(m_mutex, 0);  
	if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_TIMEOUT) {
		printf("thread WARNING: bad result from try-locking mutex\n");
	}
    return (dwWaitResult == WAIT_OBJECT_0) ? true : false; 
#else
	return (0==pthread_mutex_trylock(&m_mutex))?true:false;
#endif	
};

void PYMutex::Unlock() const
{
#ifdef _WIN32
	::ReleaseMutex(m_mutex);
#else
	pthread_mutex_unlock(&m_mutex);
#endif
}
           

繼續閱讀