我正在尝试用 C++ 编写一个虚拟去抖动函数。这是我写的:
#include <bits/stdc++.h>
using namespace std;
using namespace chrono;
function<void(int)>debounce(function<void(void)>&f , int period){
function<void(int)> fn = [&](int per){
static auto init_time = high_resolution_clock::now();
auto final_time = high_resolution_clock::now();
if ( duration_cast<milliseconds>(final_time - init_time).count() > per){
f();
}
init_time = final_time;
};
return fn;
}
int main(void){
int x = 0;
function<void(void) > f = [&x](void){
x++;
};
function<void(int)> xdf = debounce(f , 30);
std::this_thread::sleep_for(milliseconds(300));
xdf(300);
xdf(300);
std::this_thread::sleep_for(milliseconds(300));
xdf(300);
if(x>=3 || x != 2){
cout << "Debounce failed" << '\n';
}else {
cout << "Successful" << '\n';
}
return 0;
}
但这不太好用。有什么方法可以让我不必将时间限制传递给 xdf 函数吗?也许这不是正确的代码。你能给出一些其他的想法来实施或纠正这个想法吗?
您链接的 go 实现的单词到单词的转换。
#include <bits/stdc++.h>
#include <stdexcept>
using namespace std;
using namespace chrono;
function<void()> Debounced(function<void()>&f , int period){
static auto created = high_resolution_clock::now();
// "=" allow to pass by copy all used variables (created and period)
// "&f" allow to pass by reference f variable
function<void()> fn = [=,&f](){
auto now = high_resolution_clock::now();
if (duration_cast<milliseconds>(now - created).count() > period){
f();
}
};
return fn;
}
int main(void){
int x = 0;
function<void()> f = [&x](){
x++;
};
auto dbf = Debounced(f, 500);
for (int i = 0; i < 10; ++i) {
dbf();
}
if (x != 0) {
throw std::runtime_error("Expected x not to change since it's debounced for 500ms");
}
std::this_thread::sleep_for(milliseconds(500));
for (int i = 0; i < 10; ++i) {
dbf();
}
if (x != 10) {
throw std::runtime_error("Expected x to be incremented 10 times");
}
}
我不久前制作了一个,基于 Qt C++(使用 QSharedPointer 和 QTimer),但如果您喜欢的话,您可以将其更改为基于 boost 的计时器和普通共享指针。
为了完整起见,我将代码粘贴在这里,您应该能够从注释中掌握它:
#ifndef QFUNCTIONUTILS_H
#define QFUNCTIONUTILS_H
#include <functional>
#include <QSharedPointer>
#include <QTimer>
namespace QFunctionUtils
{
// function used to cache user arguments to be able to use them once timer times out
template<typename R, class ...Args>
void debounce_args_cache(const std::function<R(Args(...args))> &funcFinish, Args(&...args))
{
funcFinish(args...);
}
typedef QSharedPointer<QMetaObject::Connection> QMetaConnPtr;
// debounce implementation
// gets a user defined function as an argument and a 'delay' in miliseconds
// returns a new function that the user can call many times, but will only execute the last
// call after 'delay' miliseconds have passed
template<typename R, class ...Args>
std::function<R(Args(...args))> debounce_internal(const std::function<R(Args(...args))> &callback, const int &delay)
{
QTimer * timer = new QTimer;
QMetaConnPtr conn = QMetaConnPtr(new QMetaObject::Connection,
[timer](QMetaObject::Connection * pConn) mutable {
// connection will be deleted when returned function goes out of scope, but not the timer
// so we need to delete it manually
delete pConn;
delete timer;
});
timer->setInterval(delay);
// return function that the user can call
return [callback, timer, conn](Args(...args)) {
// disconnect old timer callback, stop timer
QObject::disconnect(*conn);
timer->stop();
// create function that caches user arguments
std::function<void(std::function<R(Args(...args))>)> funcCache = std::bind( debounce_args_cache<R, Args...>, std::placeholders::_1, args...);
// connect new timer callback, restart timer
*conn = QObject::connect(timer, &QTimer::timeout,
[callback, timer, funcCache]() {
// stop timer
timer->stop();
// call after delay
funcCache(callback);
});
timer->start();
// return default constructed return type (just to support any return type)
return R();
};
}
// throttle implementation
// same as debounce, but also calls the given function the first time is called
template<typename R, class ...Args>
std::function<R(Args(...args))> throttle_internal(const std::function<R(Args(...args))> &callback, const int &delay)
{
QTimer * timer = new QTimer;
QMetaConnPtr conn = QMetaConnPtr(new QMetaObject::Connection,
[timer](QMetaObject::Connection * pConn) mutable {
// same logic as in debounce_internal
delete pConn;
delete timer;
});
timer->setInterval(delay);
// return function that the user can call
return [callback, timer, conn](Args(...args)) {
// disconnect old timer callback
QObject::disconnect(*conn);
// check timer (throttle) state
if (!timer->isActive())
{
// connect new timer callback
*conn = QObject::connect(timer, &QTimer::timeout,
[timer]() {
// stop timer only (this happens if user did not call the function during the delay)
timer->stop();
});
// start timer to start throttle
timer->start();
// call inmediatly and return
return callback(args...);
}
// create function that caches user arguments
std::function<void(std::function<R(Args(...args))>)> funcCache = std::bind( debounce_args_cache<R, Args...>, std::placeholders::_1, args...);
// connect new timer callback
*conn = QObject::connect(timer, &QTimer::timeout,
[callback, timer, funcCache]() {
// stop timer
timer->stop();
// call after delay
funcCache(callback);
});
// return default constructed return type (just to support any return type)
return R();
};
}
// for any class implementing operator() (e.g. lambdas)
template<typename T> struct functor_traits : functor_traits<decltype(&T::operator())>
{ };
// specialization - lambda
template<typename C, typename R, typename... Args>
struct functor_traits<R(C::*)(Args...) const>
{
using lambda_type = std::function<R(Args...)>;
};
// specialization - lambda mutable
template<typename C, typename R, typename... Args>
struct functor_traits<R(C::*)(Args...)>
{
using lambda_type = std::function<R(Args...)>;
};
// specialization - function pointer
template<class R, class... Args>
struct functor_traits<R(*)(Args...)>
{
using lambda_type = std::function<R(Args...)>;
};
// wrap any of the specializations above into an std::function
template<typename T>
auto make_std_function(const T& fn) -> typename functor_traits<T>::lambda_type
{
return typename functor_traits<T>::lambda_type(fn);
}
// convert callback into std::function and call internal debounce
template<typename T>
auto Debounce(T callback, const int &delay) -> typename functor_traits<T>::lambda_type
{
return debounce_internal(make_std_function(callback), delay);
}
// convert callback into std::function and call internal throttle
template<typename T>
auto Throttle(T callback, const int &delay) -> typename functor_traits<T>::lambda_type
{
return throttle_internal(make_std_function(callback), delay);
}
}
#endif // QFUNCTIONUTILS_H
我也遇到过这个,并想扩展@Erwan的答案:
#include <chrono>
#include <functional>
class DebouncedAction
{
public:
explicit DebouncedAction(std::function<void()> callback, int period)
: interval(std::chrono::milliseconds(period)),
lastCallTime(),
callback(std::move(callback)) {}
void invoke()
{
auto now = std::chrono::high_resolution_clock::now();
// Call immediately on the first invocation or on the next interval
if (lastCallTime == std::chrono::high_resolution_clock::time_point() ||
now - lastCallTime >= interval)
{
callback();
lastCallTime = now;
}
}
private:
std::chrono::milliseconds interval;
std::chrono::high_resolution_clock::time_point lastCallTime;
std::function<void()> callback;
};