Re: throwing dtors...
"Chris M. Thomasson" <no@spam.invalid> wrote in message
news:k4XEk.16199$hX5.2021@newsfe06.iad...
Is it every appropriate to throw in a dtor? I am thinking about a simple
example of a wrapper around a POSIX file...
[...]
Thank you all (e.g., anon, James Kanze and Paavo Helde) for your excellent
insight. After absorbing your input, I think the way to go would be
something like; thread-safety wrt the `file::m_handle' member aside for a
moment:
_________________________________________________________________
#if ! defined(FILE_DTOR_UNEXPECTED)
# define FILE_DTOR_UNEXPECTED(mp_file) assert(false)
#endif
class file {
FILE* m_handle;
public:
struct error {
struct base {
int const m_status;
base(int const status) : m_status(status) {}
};
struct bad_descriptor : public base {
bad_descriptor() : base(EBADF) {}
};
struct exceeds_offset : public base {
exceeds_offset() : base(EFBIG) {}
};
struct process_orphaned : public base {
process_orphaned() : base(EIO) {}
};
struct no_free_space : public base {
no_free_space() : base(ENOSPC) {}
};
struct pipe_not_for_reading : public base {
pipe_not_for_reading() : base(EPIPE) {}
};
struct non_existing_device : public base {
non_existing_device() : base(ENXIO) {}
};
struct already_closed : public base {
already_closed() : base(0) {}
};
struct unknown : public base {
unknown() : base(0) {}
};
static void throw_status(int const status) {
assert(! status);
switch (status) {
case EBADF:
throw bad_descriptor();
case EFBIG:
throw exceeds_offset();
case EIO:
throw process_orphaned();
case ENOSPC:
throw no_free_space();
case EPIPE:
throw pipe_not_for_reading();
case ENXIO:
throw non_existing_device();
default:
throw unknown();
}
}
};
private:
int prv_close() throw() {
int status;
do {
status = std::fclose(m_handle);
} while (status == EOF && errno == EINTR);
return status;
}
public:
bool close(bool handle_eagain = true, unsigned backoff = 1) {
if (! m_handle) {
throw error::already_closed();
}
retry:
int status = prv_close();
if (status == EOF) {
if (errno != EAGAIN) {
error::throw_status(errno);
} else if (handle_eagain) {
sleep(backoff);
goto retry;
}
return false;
}
m_handle = NULL;
return true;
}
file(/* [...] */) : m_handle(NULL) {
// [...];
}
// [...];
~file() throw() {
if (m_handle) {
if (prv_close() == EOF) {
FILE_DTOR_UNEXPECTED(m_handle);
}
}
}
};
_________________________________________________________________
And explicitly document that `file::close()' returns true if everything
worked, false if the file was non-blocking and the operation would have
blocked, or throws if shi% hit the fan. Then note that `file::close()' only
returns false when the `file::close(handle_eagain)' parameter is false.
Although James Kanze seems to suggest that `file::close()' should return a
error code; perhaps the value from errno. However, this approach requires
the user to decipher the errno value and act accordingly. Where the
fine-grain exception thing my example code uses decodes errno into specific
exception types that the user can catch. AFAICT, I think the fine-grain
exceptions are more C++'ish than return specific error codes. The fact that
`file::close()' returns a simple bool when a `EAGAIN' is encountered should
be okay.
Now an application could do something like; taking my file-copy example:
_________________________________________________________________
void foo() {
try {
file src(...);
file dest(...);
dest.copy(src);
src.close();
dest.close();
src.delete();
} catch (file::error::bad_descriptor const& e) {
// [...];
} catch (file::error::exceeds_offset const& e) {
// [...];
} catch (file::error::process_orphaned const& e) {
// [...];
} catch (file::error::no_free_space const& e) {
// [...];
} catch (file::error::pipe_not_for_reading const& e) {
// [...];
} catch (file::error::non_existing_device const& e) {
// [...];
} catch (file::error::already_closed const& e) {
// [...];
} catch (file::error::unknown const& e) {
// [...];
}
}
_________________________________________________________________
or if the application does not care which "specific" error caused things to
go bad it could do:
_________________________________________________________________
void foo() {
try {
file src(...);
file dest(...);
dest.copy(src);
src.close();
dest.close();
src.delete();
} catch (file::error::base& e) {
// [...];
}
}
_________________________________________________________________
Does that look Kosher to you C++ gurus?
;^)