C++ 11可变参数接口设计在模板编程中应用的一点点总结

概述

  本人对模板编程的应用并非很深,若要用一句话总结我个人对模板编程的理解,我想说的是:模板编程是对类定义的弱化。

  如何理解“类定义的弱化”?

  一个完整的类有如下几部分组成:

  1. 类的名称;
  2. 类的成员变量(或属性,C#中属性和成员变量还是有区别的);
  3. 类的成员方法;

  从编译器的角度看,我们必须明确指定以上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、类成员方法函数的参数个数。


  本总结包含如下两部分:

  1. C++11可变参数设计的简单介绍。
  2. 通过“观察者模式”示例进一步了解C++11可变参数设计在实际编程中的应用。
    1. 使用可变参数前
    2. 使用可变参数后
  3. 总结

C++11可变参数设计的简单介绍

  我们下面通过一个简单的代码来认识可变参数的“形状”:

template<typename ... Args> void Test(Args ... args)
{
}
int main(int argc, char* argv[])
{
Test(, , , , "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(, , , , "abc");
}

通过“观察者模式”示例进一步C++11可变参数设计在实际编程中的应用。

  下面我们通过鼠标消息事件分发实例,不使用可变参数列表和使用可变参数列表设计对比二者实现效果的差别。

  一、使用可变参数

#include <list>
#include <iostream>
#include <mutex>
#include <algorithm>
class IListener //监听者接口类
{
public:
virtual void OnPress(double dXpos, double dYpos) = ;
virtual void OnCallBack(std::string sParam,int iParam) = ;
}; 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",); //执行消息分发动作
dispatcher.DispatcherPress(, ); //执行消息分发动作
}

  运行结果如下:

C++ 11可变参数接口设计在模板编程中应用的一点点总结

  上面代码为非常常用的观察者模式设计的代码,如果您觉得以上代码难懂,直接拷贝到电脑上调试一下会相对容易些。


  我们来总结以上代码的问题点:

  如下红色代码段重复代码是我们经常看到,但在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) = ;
virtual void OnCallBack(std::string sParam,int iParam) = ;
}; 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", );
dispatcher.Dispatch(&IListener::OnPress, , );
}

  以上代码运行结果与之前一样,但是我们发现,不同的消息分发的时候,我们都是调用Dispatch函数进行分发,我们省去了为每个消息重新定义一个分发事件函数的部分。Dispatch函数的内部实现使用了可变参数的接口设计方法。

总结

  我们从简单的C++可变参数使用,到实际使用示例讲解,了解到了C++11可变参数在模板编程方面应用的效果是非常棒的。

  涉及知识点总结:

  1. C++11可变参数写法;
  2. std::bind方法的使用;
  3. std::mutex方法的使用;

  到此,我们的文章到此结束,感谢大家的学习,更多优秀设计请持续关注我的博客。

上一篇:Android课程---布局管理器中的线性布局


下一篇:Fully Convolutional Networks for semantic Segmentation(深度学习经典论文翻译)