使用文件描述符移动对象



我有一个类,比如说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以便根本无法复制对象,只能移动)。

实现 move 语义时,将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;
};

> n.m 打败了我,但你需要定义一个移动构造函数,它"忘记"了 moved-from 对象中的文件描述符。 像这样:

Foo (Foo &&move_from)
{
valid = move_from.valid;
fd = move_from.fd;
move_from.valid = false;
}

此外,删除复制构造函数和复制赋值运算符,并实现移动赋值运算符。 然后,您有一个可以移动但不能复制的对象,就像std::unique_ptr一样。

您可能希望完全删除复制构造函数(或提供一些逻辑以具有两个具有不同描述符的有效实例)。如果删除它们,编译器将确保始终使用移动语义。

然后,您可以声明将执行有效移动的移动构造函数:

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;
};

一点解释:

你违反了三/五/零的规则。简而言之,它说"每当你的类需要用户定义的析构函数/复制构造函数/移动构造函数时,它很可能会是其余的"。您的类确实需要您提供的析构函数,但您既没有提供复制运算符也没有提供移动运算符,这是一个错误。您必须同时提供复制构造函数/复制赋值运算符(此处已删除)和移动构造函数/移动赋值运算符。

至于为什么:编译器不够聪明,无法猜测你需要什么。忽略您的类不支持移动语义的事实(用户定义的析构函数阻止隐式创建移动构造函数),隐式移动构造函数非常非常简单。它只是调用每个类成员的std::move。基元类型的std::move也非常简单 - 它只是复制该变量,因为它是最快的操作。
它可能另一个变量归零,但这不是必需的。根据定义,变量必须保持">未知但有效的状态"。对该变量的任何更改都不符合此定义。

最新更新