C++ 将带有移动捕获的lambda传递到函数

C++ 将带有移动捕获的lambda传递到函数,c++,lambda,c++14,C++,Lambda,C++14,我最近遇到了一个很难找到的bug。我试图将lambda传递给接受std::function对象的函数。lambda正在捕获一个不可复制的对象 我想,很明显,在所有的过路之间一定会有一些复制品。我得出这个结果是因为我总是以一个错误结束:使用deleted function错误 以下是产生此错误的代码: void call_func(std::function<void()> func) { func(); } int main() { std::fstream fs{

我最近遇到了一个很难找到的bug。我试图将lambda传递给接受
std::function
对象的函数。lambda正在捕获一个不可复制的对象

我想,很明显,在所有的过路之间一定会有一些复制品。我得出这个结果是因为我总是以一个
错误结束:使用deleted function
错误

以下是产生此错误的代码:

void call_func(std::function<void()> func)
{
    func();
}

int main()
{
    std::fstream fs{"test.txt", std::fstream::out};
    auto lam = [fs = std::move(fs)] { const_cast<std::fstream&>(fs).close(); };
    call_func(lam);
    return 0;
}
void调用函数(std::function func)
{
func();
}
int main()
{
std::fstream fs{“test.txt”,std::fstream::out};
自动lam=[fs=std::move(fs)]{const_cast(fs).close();};
呼叫_func(林);
返回0;
}
我通过在
std::shared_ptr
对象中设置
std::fstream
对象的上限来解决这个问题。这很好,但我认为可能有一个更性感的方式来做到这一点

我现在有两个问题:

  • 为什么会出现这种错误
  • 我的想法是:我在
    for
    循环中生成许多
    fstream
    对象和lambda,对于每个
    fstream
    都有一个lambda写入。因此,对
    fstream
    对象的访问仅由lambda完成。我想这样做是为了一些回调逻辑。有没有像我试过的那样,用lambdas做这件事更漂亮的方法

  • 发生此错误是因为lambda具有不可复制的捕获,从而使lambda本身不可复制<代码>标准::函数

    如果您可以控制调用函数,请将其作为模板:

    template<typename T>
    void call_func(T&& func)
    {
        func();
    }
    
    int main()
    {
        std::fstream fs{"test.txt", std::fstream::out};
        auto lam = [fs = std::move(fs)] { const_cast<std::fstream&>(fs).close(); };
        call_func(lam);
    }
    

    请注意,虚拟调用的开销是不可避免的:因为您需要在同一容器中存储具有不同行为的functor,所以您本质上需要某种多态行为。因此,您可以手动实现此多态性,也可以使用
    virtual
    。我更喜欢后者。

    /OT:不要使用
    常量投射
    ,而是将lambda标记为
    可变
    。顺便说一句,您的
    常量投射
    是UB。不能修改以这种方式浇铸的对象。正如Rakete1111指出的那样,正确的方法是标记对象
    mutable
    为什么我必须使其可变?@CássioRenan这里是什么?成员变量被捕获为非常量;只有生成的
    运算符()
    被标记为
    const
    。将
    const
    const
    左值转换为非
    const
    对象以修改所述对象是完全正确的,
    const\u转换的整个点
    。当然,我不认为这是一种不好的风格,但它不是UB。或者如果是,为什么?@Julian,因为这就是如何获得一个
    操作符()
    ,它不是
    const
    -限定的,因此提供对lambda的捕获成员的读写访问权。如果知道它只运行一次,那么
    可变的
    lambda没有错。例如,将可变lambda推送到工作队列是没有风险的;呼叫_func(林);对我来说也会有同样的错误。您的其他解决方案不适合,因为调用lambda时std::fstream对象很长时间都超出范围。@Julian对不起,我不理解您的问题。您可以将一个可复制对象移动到lambda中,并且可以将其与
    std::function
    一起正常使用。e、 g:仅仅因为你在移动对象,这并不意味着对象必须是不可复制的。我在第(2)条中编辑了我对你想法的看法,强调你是对的。编辑的答案,以消除不必要的变化,在OP的代码。@下划线,老实说,我不认为这两个是好的风格。但那只是我。有太多的方法可以让细微的改变引入无声的bug。在回应上面弗朗索瓦的评论时,我引用了其中一种方式。另外,
    const_cast
    还有一个问题,即如果您确实向lambda传递了一个const对象,它将导致UB而不是编译器错误(正如
    mutable
    lambda所做的那样)。
    #include <algorithm>
    #include <fstream>
    #include <iterator>
    #include <utility>
    #include <memory>
    #include <sstream>
    #include <vector>
    
    template<typename T>
    void call_func(T&& func) {
        func();
    }
    
    // All functors have a common base, so we will be able to store them in a single container.
    struct baseFunctor {
        virtual void operator()()=0;
    };
    
    // The actual functor is as simple as it gets.
    template<typename T>
    class functor : public baseFunctor {
        T f;
    public:
        template<typename U>
        functor(U&& f)
            :    f(std::forward<U>(f))
        {}
        void operator()() override {
            f();
        }
    };
    
    // In C++17 you don't need this: functor's default constructor can already infer T.
    template<typename T>
    auto makeNewFunctor(T&& v) {
        return std::unique_ptr<baseFunctor>(new functor<T>{std::forward<T>(v)});
    }
    
    int main() {
        // We need to store pointers instead of values, for the virtual function mechanism to behave correctly.
        std::vector<std::unique_ptr<baseFunctor>> functors;
    
        // Generate 10 functors writing to 10 different file streams
        std::generate_n(std::back_inserter(functors), 10, [](){
            static int i=0;
            std::ostringstream oss{"test"};
            oss << ++i << ".txt";
            std::fstream fs{oss.str(), std::fstream::out};
            return makeNewFunctor([fs = std::move(fs)] () mutable { fs.close(); });
        });
    
        // Execute the functors
        for (auto& functor : functors) {
            call_func(*functor);
        }
    }