У меня есть класс, скажем, 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
, который я распределяю динамически?
@н.м. Как переместить его в вектор? Насколько я могу судить, использование push_back(std::move(foo))
не вызывает ни конструктор перемещения, ни оператор присваивания перемещения (я пробовал оба)
ide(std::move(foo))
должен вызвать конструктор перемещения. Ваш класс должен иметь класс, отличный от стандартного, чтобы это работало. И это работает. Опубликовать минимальный воспроизводимый пример.
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
. Для семантики перемещения требуются неконстантные объекты, чтобы члены могли обновляться после перемещения.
@RemyLebeau Правда
@RemyLebeau Я предположил, что поле valid
используется для этой цели, но ваш ответ лучше описывает это;)
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;
};
Правильный способ - переместить его. Правильный способ переместить его — установить
fd
в объекте, из которого было перемещено, значение «фиктивное/пустое/недействительное», чтобы деструктор не делал ничего, гм, деструктивного.