1. 程式人生 > >【C++11】物件訊息匯流排(1)

【C++11】物件訊息匯流排(1)

部分Demo和結論引用自<深入應用C++11程式碼優化與工程>這本書

什麼是訊息匯流排?

物件的關係之間一般有:依賴、關聯、聚合、組合和繼承,他們的耦合程度也是依次加強!大規模開發過程中,物件很多,關聯關係很複雜,沒有統一、簡介的方法去管理物件的關係將會導致物件關係和蜘蛛網一樣,或者迴圈依賴問題。
基於訊息匯流排技術可以有效解決這些問題,物件通過訊息聯絡而不是直接依賴或者關聯。

訊息匯流排的關鍵技術

  • 通用的訊息型別
  • 訊息註冊
  • 訊息分發

通用的訊息定義

我們需要一種泛型的函式簽名來定義成一個通用的訊息格式。同時需要我們可以還需要定義一個主題型別,讓訂閱該主題的接受者接收此訊息。
我們可以使用:std::function<Ret(Args...)>

作為通用的函式簽名。
(所有的to_function請見本章最後)

訊息的註冊

匯流排內部維護了一個訊息列表,當需要傳送訊息時,會遍歷訊息列表,從中查詢是否有合適的訊息和訊息接收者,找到合適的接受者之後再次廣播。

訊息由:訊息主題+泛型函式組成。topic + std::function<Ret(Args...)>這個泛型函式可能是所有的可呼叫物件,例如:普通函式、成員函式、函式物件、std::function和lambda表示式。訊息的統一格式為std::function<Ret(Args...)>所以首先應該講各種呼叫型別轉換成該型別,格式統一後就可以將訊息儲存起來,以便後續分發(儲存訊息的方法見後面章節)。

接下來我們實現一個函式型別萃取的對下。

普通函式轉換為std::function

//前置宣告
template<typename T>
struct function_traits;

//普通函式
template<typename Ret, typename...Args>
struct function_traits<Ret(Args...)>
{
public:
	enum {arity = sizeof...(Args)};			//獲取引數數量
	typedef Ret function_type(Args...);		//定義函式型別
	typedef Ret return_type;
using stl_function_type = std::function<function_type>; //stl function型別 lambda可轉換成stl function typedef Ret(*pointer)(Args...); template<size_t I> struct args { static_assert(I < arity, "index is out of range"); //類似bind(_Binder) 原始碼實現,儲存引數並且隨時可用. using type = typename std::tuple_element<I, std::tuple<Args...>>::type; }; };

下面示例程式碼是驗證獲取普通函式型別。

void print(int a)
{
	cout <<"print:"<< a << endl;
}

void testNormalFunc()
{
	auto f = to_function(print);	//轉換為std::function

	cout << "args:"<<function_traits<decltype(print)>::arity << endl;	//獲取引數數量
	function_traits<decltype(print)>::args<0>::type value = 10;	//獲取引數型別
	cout << "value:" << value << endl;
	cout << typeid(f).name() << endl;

	f(11);

	auto pf = to_function_pointer(print);		//轉換為C語言格式的函式指標
	cout << typeid(pf).name() << endl;
	pf(20);
}

這裡寫圖片描述

函式指標轉換為std::function

函式指標,通常在C語言中經常使用,他的宣告方式通常如下:

typedef void(*PFUNC)(int);

所以為了我們的函式型別萃取支援函式指標,我們需要將其返回值和入參提取出來。
具體實現如下:

//函式指標
template<typename Ret, typename...Args>
struct function_traits<Ret(*)(Args...)>
	: public function_traits<Ret(Args...)> {};

測試程式碼如下:

typedef void(*PFUNC)(int);

void testFuncPointer()
{
	PFUNC pff = print;			//函式指標

	auto f = to_function(pff);	//轉換為std::function

	cout << "args:" << function_traits<decltype(print)>::arity << endl;	//獲取引數數量
	function_traits<decltype(print)>::args<0>::type value = 10;	//獲取引數型別
	cout << "value:" << value << endl;
	cout << typeid(f).name() << endl;

	f(11);
}

當然這個列印結果是和上面類似的。

提取std::function類

這個就比較容易了,入參和返回值直接提取即可。

//std functional
template<typename Ret, typename...Args>
struct function_traits<std::function<Ret(Args...)>>
	: public function_traits<Ret(Args...)> {};

測試程式碼如下:

void testStdFunciton()
{
	//多種方式1
	std::function<void(int)> pff = print;

	//多種方式2
	//std::function<void(int)> pff = std::bind(print, std::placeholders::_1);

	//其他方式省略

	//呼叫
	auto f = to_function(pff);	//轉換為std::function

	cout << "args:" << function_traits<decltype(print)>::arity << endl;	//獲取引數數量
	function_traits<decltype(print)>::args<0>::type value = 10;	//獲取引數型別
	cout << "value:" << value << endl;
	cout << typeid(f).name() << endl;

	f(11);
}

當然列印也是和上面的一致。

成員函式轉換為std::function

這裡需要特別注意的是,成員函式的呼叫一定是要有具體物件呼叫的。除非這個函式是靜態函式(這個和普通函式類似)。所以我們需要函式型別和具體的呼叫物件。

我們先看一下普通的物件的函式可以怎樣呼叫:

struct MyObj
{
	void print()
	{
		cout << "MyObj" << endl;
	}

	void printint(int b)
	{
		cout << "MyObj:"<< b << endl;
	}
};

//簡單測試2
template<typename ClassType>
void help4(void(ClassType::*f)(), ClassType& obj)
{
	(obj.*f)(); 
}
//呼叫測試

	MyObj obj;
	help4(&MyObj::print, obj);

不出意外可以打印出該列印的MyObj

接下來可以嘗試將其包裝為可呼叫的實體,例如一個std::function

//簡單測試1
template<typename ClassType>
void help3(void(ClassType::*f)(), ClassType& obj)
{
	std::function<void()> func = [&obj, f]() {
		return (obj.*f)();
	};
	func(); //具體呼叫點
}

根據以上測試我們實際使用成員函式呼叫至少有兩種方式了,剛剛的測試程式碼僅支援無入參,範圍值為void的型別,現在我們實現一個支援任意引數與返回值的help函式

//直接根據引數呼叫
template<typename ReturnType, typename ClassType, typename...Args>
void  help1(ReturnType(ClassType::*f)(Args...), ClassType& obj, Args&&...args)
{
	std::function<ReturnType(Args...)> func = [&obj, f, &args...](Args...) {
		return (obj.*f)(std::forward<Args>(args)...);
	};
	func(std::forward<Args>(args)...);
}

//返回可呼叫實體  此時Args可由具體呼叫時傳入
template<typename ReturnType, typename ClassType, typename...Args>
std::function<ReturnType(Args...)>  help2(ReturnType(ClassType::*f)(Args...), ClassType& obj)
{
	std::function<ReturnType(Args...)> func = [&obj, f](Args&&...args) {
		return (obj.*f)(std::forward<Args>(args)...);
	};
	//func(std::forward<Args>(args)...);
	return func;
}

以下是具體測試程式碼:

void test()
{
	MyObj obj;
	help1(&MyObj::printint, obj, 20);
	help2(&MyObj::printint, obj)(30);
	help3(&MyObj::print, obj);
	help4(&MyObj::print, obj);
}

這裡寫圖片描述

成員函式轉換為std::function的更多細節

剛剛我們事先的成員函式雖然支援不同入參和返回型別,但是函式型別例如const型別的函式,並沒有支援。接下里需要實現包含不同限定型別的函式。我們可使用巨集定義的方式(此方法在標準庫、STL庫中多次被使用)實現。

//member functional
#define FUNCTION_TRAITS(...) \
template<typename ReturnType, typename ClassType, typename...Args> \
struct function_traits<ReturnType(ClassType::*)(Args...) __VA_ARGS__> : \
function_traits<ReturnType(Args...)>{}; \

FUNCTION_TRAITS();
FUNCTION_TRAITS(const);
FUNCTION_TRAITS(volatile);
FUNCTION_TRAITS(const volatile);

可以看到將const等型別擴充套件到函式限定符位置。
測試程式碼如下:

struct MySt
{
	MySt(int b) : m_b(b) {}
	void sum(int a)
	{
		cout << "MySt:" << a + m_b << endl;
	}
private:
	int m_b;
};

void testMemberFunction()
{
	MySt st(20);

	//方法一:通過help函式 將成員函式呼叫封裝為lambda,自動轉換為std::function
	//auto pff = help(&MySt::sum, st, 30);
	//auto f = to_function(pff);	//轉換為std::function

	//方法二:直接包裝函式
	auto f = to_function(&MySt::sum, st);	//轉換為std::function

	cout << "args:" << function_traits<decltype(print)>::arity << endl;	//獲取引數數量
	function_traits<decltype(print)>::args<0>::type value = 10;	//獲取引數型別
	cout << "value:" << value << endl;
	cout << typeid(f).name() << endl;

	f(11);
}

這裡寫圖片描述

可呼叫物件轉換為std::function

struct CallObject
{
	void operator()()
	{
		cout << "CallObject" << endl;
	}
};

//函式物件 模板實現時需要具體物件型別
template<typename Callable>
struct function_traits : function_traits<decltype(&Callable::operator())> {};

void testFunctionObject()
{
	auto f = to_function(CallObject());	//轉換為std::function
	f();
}

將各類可呼叫物件轉換為std::function函式實現

//轉換成stl的function
template<typename Function>
typename function_traits<Function>::stl_function_type to_function(const Function& lambda)
{
	return static_cast<function_traits<Function>::stl_function_type>(lambda);
}
template<typename Function>
typename function_traits<Function>::stl_function_type to_function(Function&& lambda)
{
	return static_cast<function_traits<Function>::stl_function_type>(std::forward<Function>(lambda));
}

//轉換成仿函式指標
template<typename Function>
typename function_traits<Function>::pointer to_function_pointer(const Function& lambda)
{
	return static_cast<function_traits<Function>::pointer>(lambda);
}

小結

通過以上方式我們可以實現一個接受泛型型別的訊息,接下里我們需要將各類訊息儲存起來,以便分發呼叫。

見下一節。