C++ 11可变参数接口设计在模板编程中应用的一点点总结
概述
本人对模板编程的应用并非很深,若要用一句话总结我个人对模板编程的理解,我想说的是:模板编程是对类定义的弱化。
如何理解“类定义的弱化”?
一个完整的类有如下几部分组成:
- 类的名称;
- 类的成员变量(或属性,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(, , , , "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之前确又无法解决的问题。
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可变参数在模板编程方面应用的效果是非常棒的。
涉及知识点总结:
- C++11可变参数写法;
- std::bind方法的使用;
- std::mutex方法的使用;
到此,我们的文章到此结束,感谢大家的学习,更多优秀设计请持续关注我的博客。
C++ 11可变参数接口设计在模板编程中应用的一点点总结的更多相关文章
- c++11——可变参数模板
在c++11之前,类模板和函数模板只能含有固定数量的模板参数,c++11增加了可变模板参数特性:允许模板定义中包含0到任意个模板参数.声明可变参数模板时,需要在typename或class后面加上省略 ...
- C++模板编程中只特化模板类的一个成员函数(花样特化一个成员函数)
转自:https://www.cnblogs.com/zhoug2020/p/6581477.html 模板编程中如果要特化或偏特化(局部特化)一个类模板,需要特化该类模板的所有成员函数.类模板中大多 ...
- C++模板编程中只特化模板类的一个成员函数
模板编程中如果要特化或偏特化(局部特化)一个类模板,需要特化该类模板的所有成员函数.类模板中大多数成员函数的功能可能是一模一样的,特化时我们可能只需要重新实现1.2个成员函数即可.在这种情况下,如果全 ...
- C++11 可变参数模板
在C++11之前, 有两个典型的受制于模板功能不强而导致代码重复难看的问题, 那就 function object 和 tuple. 拿 function objects 来说, 需要一个返回类型参数 ...
- C++学习之可变参数的函数与模板
所谓可变参数指的是函数的参数个数可变,参数类型不定的函数.为了编写能处理不同数量实参的函数,C++11提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标 ...
- 如何把va_list可变参数传送到下一级函数中(如传送到printf)
最近我在一个LCD上想实现打印格式化字符串的功能,实现这样的功能可有两种方式: 一 最直接的就是自己去解析类似于printf功能的一个函数: 二 比较简单的方法是使用已有的sprintf功能,把格式化 ...
- MySQL - MySQL++在c++11环境下接口设计
安装官方提供的mysqlconnect后,可以使用mysql++库,在官方的C API上再次做一个c++面向对象封装. 这里mysql++的安装依赖于mysql-connector-c.安装参考:ht ...
- c 可变参数(variable argument)的原理及使用
本文主要介绍可变参数的函数使用,然后分析它的原理,程序员自己如何对它们实现和封装,最后是可能会出现的问题和避免措施. VA函数(variable argument function),参数个数可变函数 ...
- [11 Go语言基础-可变参数函数]
[11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...
随机推荐
- Matlab学以致用 - 曲线拟合
曲线拟合 使用Matlab自带的polyfit函数,可以很方便地根据现有样本数据进行多项式曲线拟合,为了有直观感受,先上例程,如下所示: x = -:; % 样本数据x坐标 y = *x.^ + *x ...
- (2)shiro角色资源权限
一般在web系统权限设计中,一般分为三个维度,用户,角色,资源,一个用户可以拥有多个角色,比如说可以是老师,也可以是班主任,一个角色也可以拥有多个资源. 比如老师同时拥有查看班级学生和批改作业的资源, ...
- ng2-file-upload 使用记录
最近这两周一直在修bug,修的很是痛苦,不过痛苦也是件好事,不然每天都是在做同样的事情,没有什么挑战,工作多无聊呀! 是吧. 大致说一下背景吧: 这个项目是两年前开新项目,到现在一直还在开发中,一直不 ...
- maven GroupId 和ArtifactId通常填什么
GroupID是项目组织唯一的标识符,实际对应JAVA的包的结构,是main目录里java的目录结构.ArtifactID就是项目的唯一的标识符,实际对应项目的名称,就是项目根目录的名称.
- one by one 项目 part 3
mysql error:Table 'performance_schema.session_variables' doesn't exist 打开cmd 输入:mysql_upgrade -u roo ...
- Python的logging,记录log的包
最近在做自动化测试时,想给他加上日志,所以用到logging的模块,以下是python增加log的几种方式 一.python代码配置方式(当然还有一种是可以多模块通用的一个python代码设置,这个网 ...
- jdbcTemplate in
参考 http://blog.csdn.net/gaopeng0071/article/details/75049952 使用NamedParameterJdbcTemplate public cla ...
- java内存模型:Happens-Before
有序性:Java内存模型中的程序天然有序性可以总结为一句话:如果在本线程内观察,所有操作都是有序的:如果在一个线程中观察另一个线程,所有操作都是无序的.前半句是指“线程内表现为串行语义”,后半句是指“ ...
- 22-maven-安装与配置
转载:https://blog.csdn.net/wy727764020/article/details/80595451 Maven的安装以及eclipse中配置maven 2018年06月06日 ...
- 求N的阶乘N!中末尾0的个数
求N的阶乘N!中末尾0的个数 有道问题是这样的:给定一个正整数N,那么N的阶乘N!末尾中有多少个0呢?例如:N=10,N=3628800,则N!的末尾有两个0:直接上干货,算法思想如下:对于任意一个正 ...