The code as shown should contain correct file descriptors, although it will leak descriptors on assignments. To fix it, you should change it to:
- Fix the leak in the assignment operator;
- Handle self-assignment;
- Implement a move constructor to optimize moves (and because this class is a textbook example of benefits of move construction);
- Handle errors.
For example (untested):
static int safe_dup(int fd) {
int copy = dup(fd);
if (copy < 0)
throw std::runtime_error(strerror(errno));
return copy;
}
MightyObject::MightyObject(const char* path) {
this->fd = open(path, O_RDWR | O_NONBLOCK);
if (this->fd == -1)
throw std::runtime_error(strerror(errno));
}
MightyObject::~MightyObject() {
if (this->fd != -1)
close(this->fd);
}
MightyObject::MightyObject(const MightyObject& other) {
this->fd = safe_dup(other.fd);
}
MightyObject& MightyObject::operator=(const MightyObject& other) {
if (this != &other) {
close(this->fd);
this->fd = safe_dup(other.fd);
}
return *this;
}
MightyObject::MightyObject(MightyObject&& other) {
// "move" file descriptor from OTHER to this: no duping is needed,
// we just take the descriptor and set the one in OTHER to -1, so
// it doesn't get closed by OTHER's destructor.
this->fd = other.fd;
other.fd = -1;
}
MightyObject& MightyObject::operator=(MightyObject&& other) {
// move assignment operator
close(this->fd);
this->fd = other.fd;
other.fd = -1;
}
Once you understand the move semantics, you can reduce the code repetition between the operators by deploying the copy and swap idiom.
Note that the above should be understood as a coding and understanding exercise. In production you likely wouldn't want to allocate a new file descriptor whenever your object is being copied around. A better design would make the file handle move-only and to implement MightyObject
as std::shared_ptr<Handle>
. That will completely avoid dup
and fiddling around with the copy constructor, assignment operator, etc., because all of them will be handled by shared_ptr
.