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 类型参数作为最 ...
随机推荐
- Haskell语言学习笔记(55)Data.Vector
Data.Vector Construction Prelude V> import Data.Vector as V Prelude V> V.empty [] Prelude V> ...
- docker上传镜像
已经存在镜像 docker tag conductor:ui docker.io/mhcvs2/mhc docker push docker.io/mhcvs2/mhc
- zabbix超级乱码解决问题
个人而言,网上那些方法并不顶什么用,研究了一番总结出了自己的一个方法. 像这种,出现了严重乱码,也不是像网上说的那样把楷体复制过去就好.事实证明我复制过去好多次了,然而并没有什么用. 下面是我自己总结 ...
- cmd dos bat 深度变量文件夹指定文件
echo off ::指定起始文件夹 :: 指定文件夹 set DIR = abc :: d:/abc 改脚本放在d: set DIR="%cd%" echo DIR=%DIR% ...
- SpringBoot配置文件YML 注意事项
YML读取注意事项 使用YML时遇到的坑: 最近在做项目时,遇到了一些在读取YML配置时发生的问题,在这里写一并写下来,希望给自己以及大家一个提示,能尽量避免在读取配置文件时发生这些错误,给开发带来不 ...
- 使用Fiddler发送POST请求
使用Fiddler发送POST请求 在测试过程中,有时会遇到需要修改请求中带的参数,去验证权限的安全问题,但是一些post请求,我们在浏览器中不能直接修改他的参数,然后去提交验证. 而fiddler可 ...
- ECMAScript6新特性之Reflect
一 Reflect.ownKeys()获取对象属性. 可枚举的.不可枚举的.自有的.继承的. let fruit = { '2' : 'mango', [Symbol.for('pink')] : ' ...
- iOS9 UIWindow rootViewController
在iOS9中App被其他应用唤起的时候Crash,正常启动或者调试模式都不会Crash. 通过XCode - Window -Device,查看设备的log,如下 Assertion failure ...
- pymysql基本的使用方法
1.导入模块+创建连接 import pymysql # 1.通过python去连接数据库 conn = pymysql.connect(host="127.0.0.1",port ...
- collections系列之OrderedDict【有序字典】与DefaultDict【默认字典】
今天来向大家介绍一下collections系列中的OrderedDict和DefaultDict,这两种类均是通过collections来创建的,均是对dict字典加工,所有都继承了dict字典的方法 ...