【C++11】对象消息总线(1)

本系列文档从属于:C++11应用实践系列

部分Demo和结论引用自<深入应用C++11代码优化与工程>这本书

消息总线

      • 什么是消息总线?
      • 消息总线的关键技术
      • 通用的消息定义
      • 消息的注册
        • 普通函数转换为std::function
        • 函数指针转换为std::function
        • 提取std::function类
        • 成员函数转换为std::function
        • 成员函数转换为std::function的更多细节
        • 可调用对象转换为std::function
        • 将各类可调用对象转换为std::function函数实现
      • 小结

什么是消息总线?

对象的关系之间一般有:依赖、关联、聚合、组合和继承,他们的耦合程度也是依次加强!大规模开发过程中,对象很多,关联关系很复杂,没有统一、简介的方法去管理对象的关系将会导致对象关系和蜘蛛网一样,或者循环依赖问题。
基于消息总线技术可以有效解决这些问题,对象通过消息联系而不是直接依赖或者关联。

消息总线的关键技术

  • 通用的消息类型
  • 消息注册
  • 消息分发

通用的消息定义

我们需要一种泛型的函数签名来定义成一个通用的消息格式。同时需要我们可以还需要定义一个主题类型,让订阅该主题的接受者接收此消息。
我们可以使用:std::function作为通用的函数签名。
(所有的to_function请见本章最后)

消息的注册

总线内部维护了一个消息列表,当需要发送消息时,会遍历消息列表,从中查找是否有合适的消息和消息接收者,找到合适的接受者之后再次广播。

消息由:消息主题+泛型函数组成。topic + std::function这个泛型函数可能是所有的可调用对象,例如:普通函数、成员函数、函数对象、std::function和lambda表达式。消息的统一格式为std::function所以首先应该讲各种调用类型转换成该类型,格式统一后就可以将消息保存起来,以便后续分发(保存消息的方法见后面章节)。

接下来我们实现一个函数类型萃取的对下。

普通函数转换为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);
}

【C++11】对象消息总线(1)_第1张图片

函数指针转换为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 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)...);
	return func;
}

以下是具体测试代码:

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

【C++11】对象消息总线(1)_第2张图片

成员函数转换为std::function的更多细节

刚刚我们事先的成员函数虽然支持不同入参和返回类型,但是函数类型例如const类型的函数,并没有支持。接下里需要实现包含不同限定类型的函数。我们可使用宏定义的方式(此方法在标准库、STL库中多次被使用)实现。

//member functional
#define FUNCTION_TRAITS(...) \
template \
struct function_traits : \
function_traits{}; \

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);
}

【C++11】对象消息总线(1)_第3张图片

可调用对象转换为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);
}

小结

通过以上方式我们可以实现一个接受泛型类型的消息,接下里我们需要将各类消息保存起来,以便分发调用。

见下一节。

你可能感兴趣的:(C++)