[英]Moving an object with a file descriptor
我有一个类,比如Foo
,定义如下:
class Foo {
public:
Foo(int i) {
valid = false;
char devname[] = "/dev/device0";
sprintf(devname, "/dev/device%d", i);
fd = open(devname, O_RDWR);
if (fd > 0) {
valid = true;
}
~Foo() {
if (valid) {
close(fd);
}
}
bool valid;
private:
int fd;
};
我还有另一个类,比如Bar
,定义如下:
class Bar {
public:
Bar() {
for (int i = 0; i < 4; i++) {
Foo foo(i);
if (foo.valid) {
vector_of_foos.push_back(foo);
}
}
}
std::vector<Foo> vector_of_foos;
};
问题在于, push_back
会复制Foo对象,该对象将复制fd
属性。 然后,将调用原始Foo
对象的析构函数,该对象将关闭fd
指向的文件,从而使fd
无效。
不幸的是,我无法使用emplace_back
因为我需要实例化Foo
对象, 然后再将其添加到vector_of_foos
向量中,以便可以检查valid
属性。
我也尝试过使用std::move
但是一旦原始Foo
对象超出范围(关闭文件),它仍然会调用析构函数。
建议像这样管理资源的方法是什么? 我应该使用智能指针数组吗? 我的vector_of_foos
应该改为std::vector<Foo *>
,在其中我维护动态分配的指向Foo
的指针的向量吗?
Foo
需要一个复制构造函数和一个复制赋值运算符,以便可以dup()
复制源对象的fd
(或者,您需要delete
它们,以便完全不能复制Foo
对象,只能移动它)。
在实现移动语义时,在将fd
值从移动对象移动到移动对象之后,您需要更新移动对象,以便其fd
不再引用有效的文件描述符。 只需将其设置为-1,这就是open()
和dup()
在错误时返回的内容。
您根本不需要valid
成员。 如果它与您的fd
不同步,这就是等待发生错误的原因。
尝试更多类似这样的方法:
class Foo {
public:
Foo(int i) {
std::string devname = "/dev/device" + std::to_string(i);
fd = open(devname.c_str(), O_RDWR);
}
Foo(const Foo &src) {
fd = dup(src.fd);
}
// or: Foo(const Foo &) = delete;
Foo(Foo &&src) : fd(-1) {
src.swap(*this);
}
~Foo() {
if (fd != -1) {
close(fd);
}
}
bool valid() const {
return (fd != -1);
}
Foo& operator=(Foo rhs) {
rhs.swap(*this);
return *this;
}
// optional: Foo& operator=(const Foo &) = delete;
void swap(Foo &other) {
std::swap(fd, other.fd);
}
private:
int fd;
};
nm击败了我,但您需要定义一个移动构造函数,以“忘记”移出对象中的文件描述符。 就像是:
Foo (Foo &&move_from)
{
valid = move_from.valid;
fd = move_from.fd;
move_from.valid = false;
}
此外,删除副本构造函数和副本分配运算符,并实现移动分配运算符。 然后,您有了一个可以移动但不能复制的对象,就像std::unique_ptr
。
您可能希望完全删除副本构造函数(或提供一些逻辑以使两个有效实例具有不同的描述符)。 如果删除它们,编译器将确保始终使用移动语义。
然后,您可以声明move构造函数,它将执行有效的移动:
class Foo {
public:
Foo(int i) {
valid = false;
char devname[] = "/dev/device0";
sprintf(devname, "/dev/device%d", i);
fd = open(devname, O_RDWR);
if (fd > 0) {
valid = true;
}
~Foo() {
if (valid) {
close(fd);
}
}
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
Foo(Foo&& other) {
valid = other.valid;
fd = other.fd;
other.valid = false;
};
Foo& operator=(Foo&& other) {
valid = other.valid;
fd = other.fd;
other.valid = false;
};
bool valid;
private:
int fd;
};
一点解释:
您违反了“三/五/零 ” 规则 。 简而言之,它说:“每当您的类需要用户定义的析构函数/复制构造函数/移动构造函数时,其余所有可能。” 您的类确实需要您提供的析构函数,但是您没有提供复制或移动运算符,这是一个错误。 您必须同时提供复制构造函数/复制赋值运算符(此处已删除)和移动构造函数/移动赋值运算符。
至于原因:编译器不够聪明,无法猜测您需要什么。 忽略类不支持移动语义的事实(用户定义的析构函数可防止隐式创建move构造函数),隐式move构造函数非常非常简单。 它只是在每个类成员上调用std::move
。 对于原始类型, std::move
也非常简单-它只是复制该变量,因为这是最快的操作。
它可以将其他变量置零,但这不是必需的。 根据定义,变量必须保留为“ 未知但有效状态 ”。 对该变量的任何更改都不适合该定义。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.