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