概述
本人對模板編程的應用並非很深,若要用一句話總結我個人對模板編程的理解,我想說的是:模板編程是對類定義的弱化。
如何理解“類定義的弱化”?
一個完整的類有如下幾部分組成:
- 類的名稱;
- 類的成員變量(或屬性,C#中屬性和成員變量還是有區別的);
- 類的成員方法;
從編譯器的角度看,我們必須明確指定以上3部分,才算完整地定義了一個類並且編譯通過。
所謂的“類弱化”,是指類的設計者在定義類的時候,並沒有完整定義一個類,而是把類的其中一部分的定義留給類的使用者。
從傳統才c++98看,通過模板類,使用者可以參與定義類的部分有:
1、成員變量的類型;
2、類成員方法參數的類型;
//實例代碼
template<class T> class Test { public: template<class Param>void TestWork(Param p) { } private: T m_t; };
模板功能大大提高了類設計者代碼的可拓展性和可維護性。
OK我們切入主題,C++11可變參數接口在“類弱化”方面有什么提高呢?
總結地來說,我們可以增加一點,使用者可以參與定義類的部分有:
3、類成員方法函數的參數個數。
本總結包含如下兩部分:
C++11可變參數設計的簡單介紹
我們下面通過一個簡單的代碼來認識可變參數的“形狀”:
template<typename ... Args> void Test(Args ... args) { } int main(int argc, char* argv[]) { Test(1, 2, 3, 4, "abc"); }
以上代碼非常簡單,我們在main函數中調用Test函數時,可以傳遞任意多個參數,且參數的類型也是任意的。如果我們在VS(VS2010以上版本)下斷點調試,我們可以看到Test函數的參數。其書寫形式也非常簡單,C++11拓展了模板的書寫形式,“typename ... Args”就可以定義一個可變從參數類型。
作為簡單的介紹,我覺得再也已經沒有什么可以在說的了,網上也有很多例子,經典例子就是我們把傳給Test函數的值都打印出來,代碼如下:
#include <iostream> template<typename T> void Test(T t) //當參數個數等於1是,進入此函數 { std::cout << t << std::endl; } template<typename T,typename ... Args> void Test(T t, Args ... args) //當參數個數大於1是,進入此函數 { std::cout << t << std::endl; Test(args...); //循環調用Test函數 } int main(int argc, char* argv[]) { Test(1, 2, 3, 4, "abc"); }
通過“觀察者模式”示例進一步C++11可變參數設計在實際編程中的應用。
下面我們通過鼠標消息事件分發實例,不使用可變參數列表和使用可變參數列表設計對比二者實現效果的差別。
一、使用可變參數前
#include <list> #include <iostream> #include <mutex> #include <algorithm> class IListener //監聽者接口類 { public: virtual void OnPress(double dXpos, double dYpos) = 0; virtual void OnCallBack(std::string sParam,int iParam) = 0; }; template<class T> class DispatcherBase { public: std::list<T*>& GetListeners() { return m_listeners; } void AddListener(T* tparam)//1、添加注冊listener { auto itor = std::find_if(m_listeners.begin(), m_listeners.end(), find_listener(tparam)); if (itor != m_listeners.end()) return; m_listeners.push_back(tparam); } void RemoveListener(T* tparam) //2、移除注冊的listener { auto itor = std::find_if(m_listeners.begin(), m_listeners.end(), find_listener(tparam)); if (itor == m_listeners.end()) return; m_listeners.erase(itor); } protected: std::list<T*> m_listeners; //3、listener的集合 struct find_listener//4、listener查找比較規則定義 { find_listener(T* listener) :m_listener(listener) {} bool operator()(const T* listener) { if (m_listener == listener) return true; else return false; } T* m_listener; }; std::mutex m_mtx; }; class Dispatcher : public DispatcherBase<IListener> //消息分發類 { public: void DispatcherPress(double dXpos, double dYpos) { m_mtx.lock(); auto listners = GetListeners(); for (auto itor : listners) { itor->OnPress(dXpos, dYpos); } m_mtx.unlock(); } void DispatcherCallBack(std::string sParam, int iParam) { m_mtx.lock(); auto listners = GetListeners(); for (auto itor : listners) { itor->OnCallBack(sParam, iParam); } m_mtx.unlock(); } }; class ListenerTest : public IListener //監聽者類 { public: ListenerTest(std::string sName) : m_sName(sName) { } virtual void OnPress(double dXpos, double dYpos) { std::cout <<"OnPress:"<< m_sName << std::endl; } virtual void OnCallBack(std::string sParam, int iParam) { std::cout <<"OnCallBack:"<< m_sName << std::endl; } private: std::string m_sName; }; int main() { Dispatcher dispatcher; //消息分發者實例 ListenerTest listener1("listener1"); //消息監聽者實例 ListenerTest listener2("listener2"); //消息監聽者實例 dispatcher.AddListener(&listener1); dispatcher.AddListener(&listener2); dispatcher.DispatcherCallBack("callbackEvent",1); //執行消息分發動作 dispatcher.DispatcherPress(10, 10); //執行消息分發動作 }
運行結果如下:
上面代碼為非常常用的觀察者模式設計的代碼,如果您覺得以上代碼難懂,直接拷貝到電腦上調試一下會相對容易些。
我們來總結以上代碼的問題點:
如下紅色代碼段重復代碼是我們經常看到,但在C++11之前確又無法解決的問題。
void DispatcherPress(double dXpos, double dYpos) { m_mtx.lock(); auto listners = GetListeners(); for (auto itor : listners) { itor->OnPress(dXpos, dYpos); } m_mtx.unlock(); } void DispatcherCallBack(std::string sParam, int iParam)
{ m_mtx.lock(); auto listners = GetListeners(); for (auto itor : listners) { itor->OnCallBack(sParam, iParam); } m_mtx.unlock(); }
二、使用可變參數后
下面,我們就來使用可變參數的方法簡化以上的問題:
#pragma once #include <list> #include <functional> #include <algorithm> #include <list> #include <iostream> #include <mutex> #include <algorithm> class IListener { public: virtual void OnPress(double dXpos, double dYpos) = 0; virtual void OnCallBack(std::string sParam,int iParam) = 0; }; template<class T> class Dispatcher { public: template<typename Funtype, typename... Args> void Dispatch(Funtype funtype, Args... args) //可變參數修改后的消息分發事件 { m_mtx.lock(); for (auto &itor : m_listeners) { std::bind(funtype, itor, args...)(); //使用std::bind去綁定具體對象,並調該對象的方法 } m_mtx.unlock(); } void AddListener(T* tparam) { auto itor = std::find_if(m_listeners.begin(), m_listeners.end(), find_listener(tparam)); if (itor != m_listeners.end()) return; m_listeners.push_back(tparam); } void RemoveListener(T* tparam) { auto itor = std::find_if(m_listeners.begin(), m_listeners.end(), find_listener(tparam)); if (itor == m_listeners.end()) return; m_listeners.erase(itor); } protected: std::list<T*> m_listeners; struct find_listener { find_listener(T* listener) :m_listener(listener) {} bool operator()(const T* listener) { if (m_listener == listener) return true; else return false; } T* m_listener; }; std::mutex m_mtx; }; class ListenerTest : public IListener { public: ListenerTest(std::string sName) : m_sName(sName) { } virtual void OnPress(double dXpos, double dYpos) { std::cout << "OnPress:" << m_sName << std::endl; } virtual void OnCallBack(std::string sParam, int iParam) { std::cout << "OnCallBack:" << m_sName << std::endl; } private: std::string m_sName; }; int main() { Dispatcher<IListener> dispatcher; ListenerTest listener1("listener1"); ListenerTest listener2("listener2"); dispatcher.AddListener(&listener1); dispatcher.AddListener(&listener2); dispatcher.Dispatch(&IListener::OnCallBack,"callbackEvent", 1); dispatcher.Dispatch(&IListener::OnPress, 10, 10); }
以上代碼運行結果與之前一樣,但是我們發現,不同的消息分發的時候,我們都是調用Dispatch函數進行分發,我們省去了為每個消息重新定義一個分發事件函數的部分。Dispatch函數的內部實現使用了可變參數的接口設計方法。
總結
我們從簡單的C++可變參數使用,到實際使用示例講解,了解到了C++11可變參數在模板編程方面應用的效果是非常棒的。
涉及知識點總結:
- C++11可變參數寫法;
- std::bind方法的使用;
- std::mutex方法的使用;
到此,我們的文章到此結束,感謝大家的學習,更多優秀設計請持續關注我的博客。