是否可以避免将参数复制到 lambda 函数?

Is it possible to avoid copying arguments to a lambda function?

本文关键字:lambda 函数 复制 可以避免 参数 是否      更新时间:2023-10-16

我想使用 Handle 管理文件描述符,我想使用 lambda 表达式来处理它们。我想使用 RAII 来管理底层文件描述符。一种选择是处理描述符的无效值(例如 -1(。但是,我希望句柄始终有效。

我发现我似乎无法避免至少调用一次复制构造函数。这是一个工作示例:

#include <fcntl.h>
#include <unistd.h>
#include <functional>
#include <system_error>
#include <iostream>
class Handle
{
public:
Handle(int descriptor) : _descriptor(descriptor) {}
~Handle()
{
std::cerr << "close(" << _descriptor << ")" << std::endl;
::close(_descriptor);
}
Handle(const Handle & other) : _descriptor(::dup(other._descriptor))
{
std::cerr << "dup(" << other._descriptor << ") = " << _descriptor << std::endl;
if (_descriptor == -1) throw std::system_error(errno, std::generic_category(), "dup");
}
int descriptor() const { return _descriptor; }
private:
int _descriptor;
};
Handle open_path(const char * path)
{
return ::open("/dev/random", O_RDONLY);
}
void invoke(std::function<void()> & function)
{
function();
}
int main(int argc, const char * argv[]) {
// Using auto f = here avoids the copy, but that's not helpful when you need a function to pass to another function.
std::function<void()> function = [handle = open_path("/dev/random")]{
std::cerr << "Opened path with descriptor: " << handle.descriptor() << std::endl;
};
invoke(function);
}

该程序的输出是:

dup(3) = 4
close(3)
Opened path with descriptor: 4
close(4)

我知道句柄正在被复制,因为它是在std::function中按值分配的,但我的印象是在某些情况下可以分配堆std::function这可能会避免复制(我想这不会发生(。

有许多选项,例如堆分配,或使用选中的哨兵值(例如-1(。但是,我希望有一个句柄始终有效的不变性。这有点像风格和不变性的问题。

有没有办法在std::function的堆栈帧中构造句柄以避免复制,还是需要采取不同的方法?

也许作为补充一点:我们可以在多大程度上依赖std::function来避免在创建时复制它的参数?

首先,让我们解决这个问题:std::function与 lambda 完全正交。我写了一篇文章,"将函数传递给函数">,应该阐明它们之间的关系,并说明可用于在现代C++中实现高阶函数的各种技术。

在此处使用auto f =可以避免复制,但当您需要将一个函数传递给另一个函数时,这没有帮助。

我不同意。您可以在invoke中使用模板或类似function_view的东西(请参阅 LLVM 的FunctionRef生产就绪实现,或我的文章了解另一个简单实现(:

template <typename F>
void invoke(F&& function)
{
std::forward<F>(function)();
}

void invoke(function_view<void()> function)
{
function();
}

依靠省略或随std::function移动是不够的。由于std::function要求是可复制的,因此您总是有可能在其他地方意外复制Handle

相反,您需要做的是将Handle包装在不会在复制时调用复制构造函数的东西中。一个明显的选择是指针。一个明显的指针选择是像std::shared_ptr这样的管理指针。

我对用于测试的Handle类进行了一些更改(dtor,ctor,copy ctor 的打印语句(,因此我将首先展示这些更改:

class Handle
{
public:
Handle(int descriptor) : _descriptor(descriptor) {std::cerr<<"Default ctor, descriptor: " << _descriptor << std::endl;}
~Handle()
{
std::cerr << "Dtor. close(" << _descriptor << ")" << std::endl;
}
Handle(const Handle & other) : _descriptor(other._descriptor+1)
{
std::cerr << "Copy ctor. dup(" << other._descriptor << ") = " << _descriptor << std::endl;
}
int descriptor() const { return _descriptor; }
private:
int _descriptor;
};

接下来,让我们修改open_path以返回一个shared_ptr

std::shared_ptr<Handle> open_path(const char * path)
{
return std::make_shared<Handle>(0);
}

然后我们将在main中对我们的 lambda 进行轻微修改:

std::function<void()> function = [handle = open_path("/dev/random")]{
std::cerr << "Opened path with descriptor: " << handle->descriptor() << std::endl;
};

我们的输出现在变为:

Default ctor, descriptor: 0
Opened path with descriptor: 0
Dtor. close(0)

现场演示