概述

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

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

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

  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之前确又无法解决的问题。

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方法的使用;

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

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

  1. c++11——可变参数模板

    在c++11之前,类模板和函数模板只能含有固定数量的模板参数,c++11增加了可变模板参数特性:允许模板定义中包含0到任意个模板参数.声明可变参数模板时,需要在typename或class后面加上省略 ...

  2. C++模板编程中只特化模板类的一个成员函数(花样特化一个成员函数)

    转自:https://www.cnblogs.com/zhoug2020/p/6581477.html 模板编程中如果要特化或偏特化(局部特化)一个类模板,需要特化该类模板的所有成员函数.类模板中大多 ...

  3. C++模板编程中只特化模板类的一个成员函数

    模板编程中如果要特化或偏特化(局部特化)一个类模板,需要特化该类模板的所有成员函数.类模板中大多数成员函数的功能可能是一模一样的,特化时我们可能只需要重新实现1.2个成员函数即可.在这种情况下,如果全 ...

  4. C++11 可变参数模板

    在C++11之前, 有两个典型的受制于模板功能不强而导致代码重复难看的问题, 那就 function object 和 tuple. 拿 function objects 来说, 需要一个返回类型参数 ...

  5. C++学习之可变参数的函数与模板

    所谓可变参数指的是函数的参数个数可变,参数类型不定的函数.为了编写能处理不同数量实参的函数,C++11提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标 ...

  6. 如何把va_list可变参数传送到下一级函数中(如传送到printf)

    最近我在一个LCD上想实现打印格式化字符串的功能,实现这样的功能可有两种方式: 一 最直接的就是自己去解析类似于printf功能的一个函数: 二 比较简单的方法是使用已有的sprintf功能,把格式化 ...

  7. MySQL - MySQL++在c++11环境下接口设计

    安装官方提供的mysqlconnect后,可以使用mysql++库,在官方的C API上再次做一个c++面向对象封装. 这里mysql++的安装依赖于mysql-connector-c.安装参考:ht ...

  8. c 可变参数(variable argument)的原理及使用

    本文主要介绍可变参数的函数使用,然后分析它的原理,程序员自己如何对它们实现和封装,最后是可能会出现的问题和避免措施. VA函数(variable argument function),参数个数可变函数 ...

  9. [11 Go语言基础-可变参数函数]

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

随机推荐

  1. 【转】Java自学之路——by马士兵

    作者:马士兵老师 JAVA自学之路 一:学会选择 为了就业,不少同学参加各种各样的培训. 决心做软件的,大多数人选的是java,或是.net,也有一些选择了手机.嵌入式.游戏.3G.测试等. 那么究竟 ...

  2. Inteiilj IDEA 团队代码格式规范

    目录 Intellij IDEA code format Tabs and Indents Spaces Wrapping and Braces Imports 更新 Intellij IDEA co ...

  3. delphi修改QQ快捷方式的目标地址达到在启动QQ的同时也能运行自己想要启动的EXE可执行文件

    delphi修改QQ快捷方式的目标地址达到在启动QQ的同时也能运行自己想要启动的EXE可执行文件. 直接上代码,自已体会 !! Unit1.pas代码如下: unit Unit1; interface ...

  4. HttpClientUtil 工具类

    /* * * * FileName: s.java * * Description:TODO(用一句话描述该文件做什么) * * Created: jiangzhanghong 2017年11月14日 ...

  5. Hive—简单窗口分析函数

    hive 窗口分析函数 : jdbc:hive2:> select * from t_access; +----------------+---------------------------- ...

  6. 我在JS上解惑之路1

    1.为什么既然存在等号(==)非等号  (!=),又会有全等号(===)非全等号(!==)? *唯一的不同是后者判断时不进行类型转换. 例:var sNum = "66"; var ...

  7. thymeleaf 拼接字符串与变量

    参考https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html <span th:text="'The name o ...

  8. phpStudy6——php导出可以设置样式的excel表格

    前言: 一般的后台管理页面肯定少不了excel表格导出的功劳,尤其是那些电商平台的订单导入导出,用户列表的导入导出等,那么本文就介绍php是如何导出excel表格的. php导出excel方法有很多, ...

  9. virtualbox Linux与Windows共享文件

    安装virtualbox增强功能 在Windows下新建文件用于共享,点设置->共享文件夹->添加共享文件,制定路径和名称(名称用于Linux中挂载使用)(选择固定分配) 在Linux中m ...

  10. 三、oracle 用户管理一

    三.oracle 用户管理一 一.创建用户概述:在oracle中要创建一个新的用户使用create user语句,一般是具有dba(数据库管理员)的权限才能使用.create user 用户名 ide ...