繁体   English   中英

我可以在没有 std::function 的情况下捕获 lambda 个变量吗?

[英]Can I capture lambda variables without std::function?

是否可以在不使用std::function的情况下获取 lambda 的捕获值? 我问是因为我想将捕获的副本放入我自己的 memory 中, std::function不能这样做,因为它们不支持自定义分配器。

(我认为std::function缺少分配器支持是有充分理由的,也许在 lambda 中捕获值背后的逻辑非常难以实现?但如果可能的话,我想自己尝试一下。)

背景:我要求在 C++ 中了解有关 lambda 的更多信息。我想将捕获的值和引用指针放置在过度对齐的 memory 中,用于我作为学术练习编写的线程池系统。 我也非常喜欢使用自动捕获的简洁编写 lambda,我认为它会成为一个非常简单的“工作”编写界面。

class MyFunction {
public:

    // I want more than just the function pointer, 
    // I also want the captured value copies or references too
    // I'm unsure how to really accomplish this though.
    MyFunction & operator=( ??? ) {
        ???
    }

};

int main(){
    int captureThis = 1;
    MyFunction func = [=]()->void {
        printf("%i", captureThis);
    };
}

检查 lambda 之外的捕获变量的值看起来不太好,但至少您可以使用工厂 function 以其独特的“lambda”模板类型(也在最终类型的 auto 帮助下)生成它,以便您可以做额外的工作在线程调用和初始化之间:

#include <iostream>
#include <thread>
#include <vector>

template <typename F>
struct Callable
{
    Callable(const F && lambda):func(std::move(lambda))
    {
        
    }

    // this is what the std::thread calls
    void operator()()
    {
        // you can check the captured variable
        int out;
        func(out,false);
        std::cout<< "Callable successfully found out the value of captured variable: "<<out <<std::endl;
        func(out,true);
    }
    const F func;
};


template<typename F>
Callable<F> CallableFactory(F&& lambda)
{
    return Callable<F>(std::forward<F>(lambda)); // or std::move(lambda)
}

int main()
{
    // variable to capture
    int a=1;
    
    auto callable = CallableFactory([&](int & outputCapturedValue, bool runNow){
        // not looking good as its not possible from outside (because they are private variables & depends on implementation of C++)
        // if checking the captured variables
        if(!runNow)
        {
            outputCapturedValue = a;
            std::cout << "inside the lambda: a=" << a <<std::endl;
        }
        else
        {
            std::cout<<"algorithm runs"<<std::endl;
        }
    });
    
    
    std::vector<std::thread> threads;
    threads.emplace_back(std::move(callable));
    threads[0].join();
    
    return 0;
}

output:

inside the lambda: a=1
Callable successfully found out the value of captured variable: 1
algorithm runs

如果它只是为了让线程数组处理 lambda 数组,您可以使用智能指针和额外的容器结构在工作分配期间对它们进行装箱/拆箱:

#include <iostream>
#include <thread>
#include <vector>
#include <memory>


struct ICallable
{
    virtual void operator()()=0;
};

template <typename F>
struct Callable:public ICallable
{
    Callable(const F && lambda):func(std::move(lambda))
    {
        
    }

    // this is what the std::thread calls
    void operator()() override
    {
        func();
    }
    const F func;
};


template<typename F>
std::shared_ptr<ICallable> CallablePtrFactory(F&& lambda)
{
    return std::shared_ptr<ICallable>(new Callable<F>(std::forward<F>(lambda)));
}

struct CallableContainer
{
    std::shared_ptr<ICallable> callable;
    void operator()()
    {
        callable.get()->operator()();
    }        
};

int main()
{
    // variable to capture
    int a=1;

    // simulating work pool
    std::vector<std::shared_ptr<ICallable>> callables;
    callables.push_back(CallablePtrFactory([&](){
        std::cout<< "a="<<a<<std::endl;
    }));

    // simulating worker pool load-balancing
    std::vector<std::thread> threads;
    threads.emplace_back(CallableContainer{ callables[0] });
    threads[0].join();
    
    return 0;
}

output:

a=1

如果您正在为容器进行自定义分配,则只需为工厂使用第二个参数 function。以下示例在堆栈缓冲区上使用 placement-new。 但是 lambda 本身还有其他一些东西使得容器的大小不会被它的 lambda 改变(就像一个函数指针):

#include <iostream>
#include <thread>
#include <vector>
#include <memory>


struct ICallable
{
    virtual void operator()()=0;
};

template <typename F>
struct Callable:public ICallable
{
    Callable(const F && lambda):func(std::move(lambda))
    {
        currentSize = sizeof(*this); std::cout<<"current size = "<<currentSize <<" (useful for alignement of next element?)" <<std::endl;
    }

    // this is what the std::thread calls
    void operator()() override
    {
        func();
    }
    int currentSize;
    const F func;
    
};


template<typename F>
std::shared_ptr<ICallable> CallablePtrFactory(F&& lambda, char * buffer)
{
    return std::shared_ptr<ICallable>(
                new (buffer) Callable<F>(std::forward<F>(lambda)),
                [](ICallable *){ /* placement-new does not require a delete! */}
                );
}

struct CallableContainer
{
    std::shared_ptr<ICallable> callable;
    void operator()()
    {
        callable.get()->operator()();
    }        
};

int main()
{
    // variable to capture
    int a=1;

    char buffer[10000];

    // simulating work pool
    std::vector<std::shared_ptr<ICallable>> callables;
    
    
    callables.push_back(
        // observe the buffer for placement-new
        CallablePtrFactory([&](){
            std::cout<< "a="<<a<<std::endl;
        },buffer /* you should compute offset for next element */)
    );

  

    // simulating worker pool load-balancing
    std::vector<std::thread> threads;
    threads.emplace_back(CallableContainer{ callables[0] });
    threads[0].join();
    
    return 0;
}

output:

current size = 24 (useful for alignement of next element?)
a=1

考虑到您在评论中提到的线程池系统,您可以尝试一种多态方法:

class ThreadRoutine
{
protected:
    virtual ~ThreadRoutine() { }
public:
    virtual void run() = 0;
};

template <typename Runner>
class ThreadRoutineT
{
    // variant 1:
    Runner m_runner; // store by value;
    // variant 2:
    std::reference_wrapper<Runner> m_runner;
public:
    void run() override { m_runner(); } // call operator()
};

现在,您可以将线程例程存储在std::vector中(注意:按值指向的指针将导致 object 切片;可能是std::unique_ptr ,根据用例,经典的原始指针也可能适合)。

你甚至可以实现这两种变体,你的线程池管理器可以在线程创建中提供一个额外的参数 function 或者通过重载 function 来更优雅地区分(左值引用:创建引用包装器变体;右值引用:创建值变体,移动构造它),例如:

class ThreadManager
{
    template <typename Runner>
    void createThread(Runner& runner)
    {
        // assuming std::vector<std::unique_ptr<ThreadRoutine>>
        m_runners.emplace_back
        (
            // assuming appropriate constructor
            std::make_unique<ThreadRoutineRef>(runner)
        );
        // return some kind of thread handle or start thread directly?
        // thread handle: could be an iterator into a std::list
        // (instead of std::vector) as these iterators do not invalidate
        // if elements preceding in the list are removed
        // alternatively an id as key into a std::[unordered_]map
    }
    template <typename Runner>
    void createThread(Runner&& runner)
    {
        m_runners.emplace_back
        (
            // assuming appropriate constructor
            std::make_unique<ThreadRoutineVal>(std::move(runner))
        );
    }
}

关于 alignment 问题:特定的模板实例化将 select alignment 适用于模板参数类型,因此您不必考虑任何特别的事情。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM