C++中,为任意一个可调用对象实现一个包装器,以实现为这个可调用对象附加新的功能(本例中:输出可调用对象的执行时间)

包装器这种编程方法是内建到python的语言里的。C++也能很容易实现这个功能。

本文的包装器能为任意一个可调用对象附加一个输出执行时间的功能。我们把这个包装器实现成函数TimeMonitorFunc。下面先进行概述,然后介绍一下具体实现,最后介绍把这个包装器修改成一个简化的版本(注意,不是优化而是简化,功能也减少了)。

概述:

(1)功能:

任何一个可调用对象(例如函数)都能和TimeMonitorFunc结合在一起使用。这个包装器的功能是执行任意一个可调用对象并得出执行时间。

(2)原型:

template <typename T,typename... Args>
decltype(auto) TimeMonitorFunc(long long& time, T&& func, Args&&... params)

(3)描述:

第一个参数time是一个传出参数,表示函数执行了多少毫秒。把可调用对象传给形参func,把可调用对象的参数在func后依次传入,TimeMonitorFunc的返回值即func的返回值。

下面介绍具体实现:

#include "pch.h"
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
using namespace std;
using namespace std::chrono;

std::string ToString(int a, int b, char c)
{
	this_thread::sleep_for(seconds(1));
	return to_string(a) + to_string(b) + c;
}

int ToInt(string str)
{
	this_thread::sleep_for(seconds(1));
	return atoi(str.c_str());
}

template <typename T,typename... Args>
decltype(auto) TimeMonitorFunc(long long& time, T&& func, Args&&... params)
{
	long long  start_timestamp = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
	decltype(auto) ret = forward<decltype(func)>(func)(forward<decltype(params)>(params)...);
	long long end_timestamp = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
	time = end_timestamp - start_timestamp;
	return ret;
}


int main()
{
	long long run_duration;
        int n = 10;
	string str = TimeMonitorFunc(run_duration, ToString,n, 20, 'a');
	cout << str << " " << run_duration <<endl;
	int number = TimeMonitorFunc(run_duration, ToInt,"555");
	cout << number << " " << run_duration << endl;
	getchar();
}

TimeMonitorFunc的参数列表和语句:decltype(auto) ret = forward<decltype(func)>(func)(forward<decltype(params)>(params)...);涉及了多个C++11的名词,简单罗列这些名词,比如模板参数包、完美转发、万能引用等,这种名词堆砌不友好。可以谈一下这两行代码做了什么。我的介绍不能完备的解释这些技术,只是开了个头。

auto作为返回值是C++14特性,decltype(auto) 的作用和auto类似,也是表示返回值的类型通过推到获得,但类型的推导规则和auto略有不同,本文暂不讨论推导规则。

参数列表中的T&& func和 Args&&... params相比普通的模板形参声明多了一个&&的修饰,这用来把形参声明成万能引用形参,效果是如果传入的实参是右值,就把形参类型实例化为右值的形式,反之是左值的形式。“decltype(auto) TimeMonitorFunc(run_duration, ToString,n, 20, 'a')”实例化的结果是:“decltype(auto) TimeMonitorFunc(__int64 &,string(__cdecl &)(int,int,char),int &,int &&,char &&)”。

函数体内使用了forward,这种用法也和右值有关。根据右值的定义,不管TimeMonitorFunc的形参func和params绑定的实参是否是右值,在函数体内引用变量func和params时,func和params都是左值。在实现这个包装器时,我们希望如果TimeMonitorFunc的形参func和params绑定的实参是右值时,函数内执行func(params...)时保持func和params的右值属性不变。forward执行了在必要时把func和params转换为右值的功能。我可以透露,forward是根据<>内的信息判断该不该转换的。结合上文的代码说明forward的作用:

/*
调用
int n = 10;
string str = TimeMonitorFunc(run_duration, ToString,n, 20, 'a');
时,
forward<decltype(func)>(func)(forward<decltype(params)>(params)...);
相当于
int a = 10;
func(n, 20, 'a');
因为调用TimeMonitorFunc时,n是左值、20和’a‘是右值,传递到func的实参保持了这种左、右值属性。
*/
/*
如果修改TimeMonitorFunc的实现方式,去掉forward,即实现为:(func)(params...);
调用
int n = 10;
string str = TimeMonitorFunc(run_duration, ToString,n, 20, 'a');
时,
(func)(params...);
相当于
int a = 10; int b = 20; char c = 'a';
func(a, b, c);
虽然调用TimeMonitorFunc时,n是左值、20和’a‘是右值,但传递到func的实参都是左值。
*/

简化版本:

介绍了这么多和右值有关的事情,而且远远没有说透彻。右值似乎是个麻烦。舍弃这个麻烦有时也是可以的,不支持右值。代码如下:

template <typename T,typename... Args>
auto TimeMonitorFunc(long long& time, T& func, Args... params)
{
	long long  start_timestamp = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
	auto ret = func(params...);
	long long end_timestamp = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
	time = end_timestamp - start_timestamp;
	return ret;
}

猜你喜欢

转载自blog.csdn.net/weixin_41855721/article/details/82914130