C++ 函数消抖
2024-12-22 20:52:07+08:00
C++ 函数消抖
函数消抖 指在短时间内连续多次调用同一函数,仅最后一次调用生效。
形如:
auto debouncedFn = debounce(fn, 100);
通常将需要消抖的函数封装成一个新的函数,新的函数进行延迟后调用原函数:
- 如果在延时结束前再次调用,则重新开始延时
- 如果在延时结束前没有再次调用,则使调用原函数
示例
这个实现存在一个问题,那就是通过
thread.join();
等待上一次调用取消,会产生阻塞,不能用于实际开发。
#include <functional>
#include <chrono>
#include <thread>
#include <mutex>
#include <future>
#include <memory>
#include <exception>
namespace Utils
{
template<typename R, typename... ARGS>
std::enable_if_t<!std::is_void_v<R> && (!std::is_void_v<ARGS> && ...), void>
invoke(std::shared_ptr<std::promise<R>> promise, std::function<R(ARGS...)> fn, ARGS... args)
{
promise->set_value(fn(args...));
}
template<typename R>
void invoke(std::shared_ptr<std::promise<R>> promise, std::function<R(void)> fn)
{
promise->set_value(fn());
}
template<typename... ARGS>
void invoke(std::shared_ptr<std::promise<void>> promise, std::function<void(ARGS...)> fn, ARGS... args)
{
fn(args...);
promise->set_value();
}
void invoke(std::shared_ptr<std::promise<void>> promise, std::function<void(void)> fn)
{
fn();
promise->set_value();
}
/*************************************************************************
* @brief 函数消抖
* @param fn 希望消抖函数
* @param ms 消抖的延时时间,毫秒
* @return 一个封装了消抖功能的函数
*************************************************************************/
template<typename R, typename... ARGS>
std::function<std::shared_ptr<std::promise<R>>(ARGS...)> debounce(std::function<R(ARGS...)> fn, int ms)
{
auto lambda = [fn = std::move(fn), ms](ARGS... args) mutable -> std::shared_ptr<std::promise<R>> {
std::shared_ptr<std::promise<R>> promise = std::make_shared<std::promise<R>>();
static auto lastCallTime = std::chrono::steady_clock::now();
static bool cancel = false;
static std::thread thread;
static std::mutex mutex;
if (thread.joinable())
{
mutex.lock();
cancel = true;
mutex.unlock();
thread.join();
mutex.lock();
cancel = false;
mutex.unlock();
}
thread = std::thread([fn, ms, promise, args...]() mutable {
std::this_thread::sleep_until(lastCallTime + std::chrono::milliseconds(ms));
mutex.lock();
if (cancel)
{
mutex.unlock();
promise->set_exception(std::make_exception_ptr(std::runtime_error("cancel")));
return;
}
mutex.unlock();
// promise->set_value(fn(args...));
invoke(promise, fn, args...);
thread.detach();
});
return promise;
};
return lambda;
}
template<typename R, typename... ARGS>
std::function<std::shared_ptr<std::promise<R>>(ARGS...)> debounce(R(*fn)(ARGS...), int ms)
{
return debounce(static_cast<std::function<R(ARGS...)>>(fn), ms);
}
}; // namespace Utils
测试
int main()
{
auto fn = Utils::debounce(test, 1000);
auto p1 = fn(1, 2);
auto p2 = fn(3, 4);
auto f1 = p1->get_future();
auto f2 = p2->get_future();
f1.wait();
f2.wait();
try {
if (f1.valid())
printf("%d\n", f1.get());
} catch (std::runtime_error e) {
printf("%s\n", e.what());
}
try {
if (f2.valid())
printf("%d\n", f2.get());
} catch (std::runtime_error e) {
printf("%s\n", e.what());
}
return 0;
}
结果:
test 3 4
cancel
7
C++ 容器内元素重复析构的问题
2024-12-18 21:18:31+08:00
C++ 容器内元素重复析构的问题
说明
std::vector
这种连续空间的容器,当空间不足时需要整体重新分配内存,并将旧的数据迁移过去。
首先会使用 std::move_if_noexcept
尝试进行移动。
因此如果元素类型的移动构造函数没有标明 noexcept
则不会被调用。
之后会通过 std::uninitialized_copy
尝试进行拷贝。
这是因为移动中如果产生异常,部分源数据已经被移动,将无法恢复原状。而拷贝中如果发生异常,源数据不应改变,只要返回失败即可。
参考:
示例
示例1 - 普通的类 :
#include <cstdio>
#include <vector>
struct Demo
{
~Demo() {printf("destructor\n");}
Demo() {printf("constructor\n");}
Demo(const Demo&) {printf("copy\n");}
Demo(Demo&&) {printf("move\n");} // 没有标注 noexcept,调用拷贝
};
int main()
{
std::vector<Demo> vec;
for (size_t i = 0; i < 2; i++)
{
vec.emplace_back();
}
return 0;
}
示例2 - 普通的模板类 :
#include <cstdio>
#include <vector>
template<size_t N>
struct Demo
{
~Demo() {printf("destructor\n");}
Demo() {printf("constructor\n");}
Demo(const Demo<N>&) {printf("copy\n");}
Demo(Demo<N>&&) {printf("move\n");} // 没有标注 noexcept,调用拷贝
char data[N];
};
int main()
{
std::vector<Demo<64>> vec;
for (size_t i = 0; i < 2; i++)
{
vec.emplace_back();
}
return 0;
}
示例3 - 构造函数为模板的类 :
#include <cstdio>
#include <vector>
template<size_t N>
struct Demo
{
~Demo() {printf("destructor\n");}
Demo() {printf("constructor\n");}
// Demo<U> 和 Demo<N> 不是同一个类型,因此这不是移动构造函数
// 但是如果标注 noexcept,由于没有 默认移动构造函数 Demo(const Demo<N>&&)
// std::move_if_noexcept 会通过普通的右值引用参数匹配调用此函数
template<size_t U>
explicit Demo(Demo<U>&&) {printf("move 1\n");}
// Demo<U> 和 Demo<N> 不是同一个类型,因此这不是拷贝构造函数
// 由于有默认移动构造函数 Demo(const Demo<N>&)
// std::uninitialized_copy 会调用默认拷贝构造函数,而不会调用此函数
template<size_t U>
Demo(const Demo<U>&) {printf("copy 1\n");}
// 没有显式的拷贝和移动构造函数
// 会自动生成默认的拷贝构造函数 Demo(const Demo<N>&);
// std::uninitialized_copy 会调用 它进行浅拷贝
// 如果管理指针,析构函数会产生二次释放
// 如果删除默认拷贝构造函数:Demo(const Demo<N>&) = delete;
// template<size_t U> Demo(const Demo<U>&); 则也不会产生该实例
// std::uninitialized_copy 将无法在 U == T 时调用,只能在不同时调用
char data[N];
};
int main()
{
std::vector<Demo<64>> vec;
for (size_t i = 0; i < 2; i++)
{
vec.emplace_back();
}
return 0;
}
解决方案
当需要实现类似上述 示例3 的场景(存在类似拷贝构造函数或移动构造函数的模板函数)时,应当对真正的拷贝构造函数和真正的移动构造函数进行显式特例化。
#include <cstdio>
#include <vector>
template<size_t N>
struct Demo
{
~Demo()
{
printf("destructor %p\n", this );
delete[] data;
}
Demo()
{
printf("constructor %p\n", this);
data = new float[N];
}
// Demo<U> 和 Demo<N> 不是同一个类型,因此这不是真正的移动构造函数
template<size_t U>
explicit Demo(Demo<U>&& src) noexcept
{
printf("fake move %p\n", this);
if (U >= N)
{
data = src.data;
src.data = nullptr;
}
else
{
data = new float[N]{0};
std::copy(data, data + U, src.data);
}
}
// Demo<U> 和 Demo<N> 不是同一个类型,因此这不是真正的拷贝构造函数
template<size_t U>
explicit Demo(const Demo<U>& src)
{
printf("fake copy %p\n", this);
data = new float[N]{0};
if (U >= N)
{
std::copy(data, data + N, src.data);
}
else
{
std::copy(data, data + U, src.data);
}
}
// 显式特化真正的移动构造函数
explicit Demo(Demo<N>&& src) noexcept
{
printf("true move %p\n", this);
data = src.data;
src.data = nullptr;
}
// 显式特化真正的拷贝构造函数
explicit Demo(const Demo<N>& src) noexcept
{
printf("true copy %p\n", this);
data = new float[N]{0};
std::copy(data, data + N, src.data);
}
float* data;
};
int main()
{
// 相同类型调用真正的移动构造函数和真正的拷贝构造函数
{
Demo<64> demo1;
Demo<64> demo2{std::move_if_noexcept(demo1)};
Demo<64> demo3{demo2};
}
// 不同类型调用模板函数
{
Demo<64> demo1;
Demo<32> demo2{std::move_if_noexcept(demo1)};
Demo<128> demo3{demo2};
}
return 0;
}
==16139== Memcheck, a memory error detector
==16139== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==16139== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==16139== Command: ./a.out
==16139==
constructor 0x1ffefffbb0
true move 0x1ffefffbb8
true copy 0x1ffefffbc0
destructor 0x1ffefffbc0
destructor 0x1ffefffbb8
destructor 0x1ffefffbb0
constructor 0x1ffefffbb0
fake move 0x1ffefffbb8
fake copy 0x1ffefffbc0
destructor 0x1ffefffbc0
destructor 0x1ffefffbb8
destructor 0x1ffefffbb0
==16139==
==16139== HEAP SUMMARY:
==16139== in use at exit: 0 bytes in 0 blocks
==16139== total heap usage: 6 allocs, 6 frees, 75,008 bytes allocated
==16139==
==16139== All heap blocks were freed -- no leaks are possible
==16139==
==16139== For lists of detected and suppressed errors, rerun with: -s
==16139== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
在 enum class 中使用 bitmask 组合的方法
2024-12-18 21:18:31+08:00
在 enum class 中使用 bitmask 组合的方法
可以通过重载 |
运算符实现 bitmask 组合,例如:
enum class SystemNamespaceType
{
MOUNT = CLONE_NEWNS, // Mount Namespace
UTS = CLONE_NEWUTS, // UNIX Time-Sharing Namespace
IPC = CLONE_NEWIPC, // Inter-Process Communication Namespace
PID = CLONE_NEWPID, // Process ID Namespace
NET = CLONE_NEWNET, // Network Namespace
USER = CLONE_NEWUSER, // User Namespace
CGROUP = CLONE_NEWCGROUP, // CGroup Namespace
};
SystemNamespaceType operator | (SystemNamespaceType x, SystemNamespaceType y)
{
return static_cast<SystemNamespaceType>(static_cast<int>(x) | static_cast<int>(y));
}