我有一个这样的类,例如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
向量中,以便我可以检查{{ 1}}属性。
我也尝试过使用valid
,但是一旦原始std::move
对象超出范围并关闭文件,它仍然会调用析构函数。
推荐这样的资源管理方式是什么?我应该使用智能指针数组吗?我的Foo
应该是一个vector_of_foos
,在那里我维护一个指向std::vector<Foo *>
的指针的向量,该指针是我动态分配的吗?
答案 0 :(得分:2)
Foo
需要一个复制构造函数和一个复制赋值运算符,因此它可以dup()
复制对象的fd
(或者,您需要delete
他们{ {1}}个对象根本无法复制,只能移动)。
在实现移动语义时,将Foo
的值从移动对象移动到移动对象之后,您需要更新移动对象,以便其fd
不再引用有效的文件描述符。只需将其设置为-1,这就是fd
和open()
返回错误的结果。
您根本不需要dup()
成员。如果它与您的valid
不同步,这就是等待发生错误的原因。
尝试更多类似的方法:
fd
答案 1 :(得分:1)
n.m使我大吃一惊,但您需要定义一个移动构造函数,以“忘记”移出对象中的文件描述符。像这样:
Foo (Foo &&move_from)
{
valid = move_from.valid;
fd = move_from.fd;
move_from.valid = false;
}
此外,删除您的副本构造函数和副本分配运算符,并实现移动分配运算符。然后,您有了一个可以移动但不能复制的对象,就像std::unique_ptr
。
答案 2 :(得分:1)
您可能希望完全删除副本构造函数(或提供一些逻辑以使两个有效实例具有不同的描述符)。如果删除它们,编译器将确保始终使用移动语义。
然后,您可以声明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;
};
一些解释:
您违反了Rule of Three/Five/Zero。简而言之,它说:“每当您的类需要用户定义的析构函数/复制构造函数/移动构造函数时,其余所有可能。”您的类确实需要您提供的析构函数,但是您没有提供复制或移动运算符,这是一个错误。您必须同时提供复制构造函数/复制赋值运算符(此处已删除)和移动构造函数/移动赋值运算符。
原因:编译器不够聪明,无法猜测您需要什么。忽略类不支持移动语义的事实(用户定义的析构函数可防止隐式创建move构造函数),隐式move构造函数非常非常简单。它仅在每个类成员上调用std::move
。对于原始类型,std::move
也非常简单-它只是复制该变量,因为这是最快的操作。
它可以可以将其他变量设为零,但这不是必需的。根据定义,变量必须保留为“ 未知但有效状态”。对该变量的更改不适合该定义。