Перемещение объекта с файловым дескриптором

У меня есть класс, скажем, 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, который я распределяю динамически?

Правильный способ - переместить его. Правильный способ переместить его — установить fd в объекте, из которого было перемещено, значение «фиктивное/пустое/недействительное», чтобы деструктор не делал ничего, гм, деструктивного.

n. 1.8e9-where's-my-share m. 30.05.2019 00:13

@н.м. Как переместить его в вектор? Насколько я могу судить, использование push_back(std::move(foo)) не вызывает ни конструктор перемещения, ни оператор присваивания перемещения (я пробовал оба)

gpanders 30.05.2019 00:14
ide(std::move(foo)) должен вызвать конструктор перемещения. Ваш класс должен иметь класс, отличный от стандартного, чтобы это работало. И это работает. Опубликовать минимальный воспроизводимый пример.
n. 1.8e9-where's-my-share m. 30.05.2019 00:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
3
488
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

nm превзошел меня, но вам нужно определить конструктор перемещения, который «забывает» о вашем файловом дескрипторе в объекте, из которого был перемещен. Что-то типа:

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 для примитивных типов тоже очень просто — просто копирует эту переменную, так как это самая быстрая операция.
Это мог, возможно, обнуляет другую переменную, но это не обязательно. Согласно определению, переменная должна быть оставлена ​​в «неизвестное, но допустимое состояние». Никакое изменение этой переменной не подходит под это определение.

В Foo(const Foo&& other) и Foo& operator=(const Foo&& other) нужно убрать const. Для семантики перемещения требуются неконстантные объекты, чтобы члены могли обновляться после перемещения.

Remy Lebeau 30.05.2019 00:30

@RemyLebeau Правда

Yksisarvinen 30.05.2019 00:31

@RemyLebeau Я предположил, что поле valid используется для этой цели, но ваш ответ лучше описывает это;)

Yksisarvinen 30.05.2019 00:35
Ответ принят как подходящий

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

Другие вопросы по теме