簡體   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