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 类型参数作为最 ...
随机推荐
- c++中虚函数
虽然很难找到一本不讨论多态性的C++书籍或杂志,但是,大多数这类讨论使多态性和C++虚函数的使用看起来很难.我打算在这篇文章中通过从几个方面和结合一些例子使读者理解在C++中的虚函数实现技术.说明一点 ...
- hibernate 解决并发问题
hibernate 解决并发问题的策略有 1)设置hibernate事务隔离级别 2)hibernate中乐观锁的实现 ps:版本号是由hibernate自己维护的,我们自己只需要做以上二步即可实现乐 ...
- spring 中c3p0的优化配置
jdbc.properties driverClass=com.mysql.jdbc.Driver jdbcUrl=jdbc:mysql://localhost:3306/testdb user=ro ...
- ssh 设置反向代理
远程主机上/etc/ssh/sshd_config中,开启 GatewayPorts yes systemctl reload sshd 本地: ssh -CqTnN -R 0.0.0.0:9000: ...
- Swagger与SpringMVC整合
依赖管理 在整合之前,需要把所有使用到的依赖包全部引入.网上很多文章只是简单告诉读者引入swagger-springmvc-1.0.2.jar包,但是随后你发现这远远不够,还需要很多包,如下所示: ...
- 133克隆图 · Clone Graph
[抄题]: 克隆一张无向图,图中的每个节点包含一个 label 和一个列表 neighbors. [思维问题]: [一句话思路]: 先BFS克隆点(一个点+扩展所有邻居),再克隆邻居(一个点+扩展所有 ...
- swift UIAlertController使用 UIAlertController的宽度 为270
添加子控件 1. 有标题, alert标题高度大概 是 40, 子控件的 Y一般在40 ,如果中间有换行, \n 的高度大概是30 2.alert的宽度 是270, 设置frame 的时候注意 /// ...
- 【JVM】浅谈双亲委派和破坏双亲委派
一.前言 笔者曾经阅读过周志明的<深入理解Java虚拟机>这本书,阅读完后自以为对jvm有了一定的了解,然而当真正碰到问题的时候,才发现自己读的有多粗糙,也体会到只有实践才能加深理解,正应 ...
- canvas学习持续更新
参考:菜鸟教程,canvas教程 绘制一个简单的矩形 <body> <canvas id="myCanvas" width="200" hei ...
- css简单分页
html代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <ti ...