使用文件描述符移动对象

时间:2019-05-29 22:04:51

标签: c++

我有一个这样的类,例如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 *>的指针的向量,该指针是我动态分配的吗?

3 个答案:

答案 0 :(得分:2)

Foo需要一个复制构造函数和一个复制赋值运算符,因此它可以dup()复制对象的fd(或者,您需要delete他们{ {1}}个对象根本无法复制,只能移动)。

在实现移动语义时,将Foo的值从移动对象移动到移动对象之后,您需要更新移动对象,以便其fd不再引用有效的文件描述符。只需将其设置为-1,这就是fdopen()返回错误的结果。

您根本不需要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也非常简单-它只是复制该变量,因为这是最快的操作。
它可以可以将其他变量设为零,但这不是必需的。根据定义,变量必须保留为“ 未知但有效状态”。对该变量的更改不适合该定义。