繁体   English   中英

使用文件描述符移动对象

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM