我想使用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
来避免在创建它时复制它的参数?
答案 0 :(得分:5)
首先,让我们解决这个问题:std::function
与lambdas完全正交。我写了一篇文章"passing functions to functions",它应该澄清它们之间的关系,并说明可用于在现代C ++中实现高阶函数的各种技术。
在此处使用
auto f =
可以避免复制,但是当您需要将函数传递给其他函数时,这无用。
我不同意。您可以使用invoke
中的模板或类似function_view
的模板(请参阅LLVM&#39; FunctionRef
以获得生产就绪的实施,或者我的文章用于另一个简单实现)< / EM>:
template <typename F>
void invoke(F&& function)
{
std::forward<F>(function)();
}
void invoke(function_view<void()> function)
{
function();
}
答案 1 :(得分:2)
依靠elision或移动std::function
是不够的。由于std::function
必须是可复制的,因此您可能偶尔会在其他地方复制Handle
。
您需要做的是将Handle
包装在不会在副本上调用复制构造函数的内容中。一个明显的选择是指针。显而易见的指针选择是像std::shared_ptr
这样的manged。
我对您的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)