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));
}