1

If the resource-allocation part of the constructor of e.g. a RAII socket wrapper fails, do I just throw an exception and be done with it? Or should I go with how std::fstream seems to do it, where you need to check is_open() after you construct the object?

The former seems more in-line with the name "resource allocation is initialization", but then why does the standard library basically make you check an error code before you use the object?

I am referring to the example on the reference page for basic_fstream (paraphrased, notes added):

int main() {
  std::string filename = "test.bin";
  std::fstream s(filename);

  if (!s.is_open()) { // should my socket class require the user to do this?
    std::cout << "failed to open " << filename << '\n';
  } else {
    // ... use it
  }
}
MHebes
  • 2,290
  • 1
  • 16
  • 29
  • 1
    Not all types of failure are equal. Failing to open a file is often a legitimate and expected path so you would not want to throw an exception in that case. – Galik Sep 15 '20 at 03:58
  • 1
    It's horses for courses, since it depends on severity of a failure to open a socket. If a failure opening a socket is considered a routine occurrence that doesn't compromise ability of your application to continue running then do it like `fstream` does. Otherwise throw an exception. The problem with the `fstream` approach occurs if a failure occurs and code forgets to check (e.g. using `is_open()`) before doing things that assume no error has occurred. The problem with throwing an exception is that it forces the caller to respond to the failure, even if the caller can safely ignore it. – Peter Sep 15 '20 at 04:15

1 Answers1

5

The recommended course of action is to throw an exception on any failure that occurs during construction. Quoting the C++ FAQ:

Q. How can I handle a constructor that fails?

A. Throw an exception.

Constructors don’t have a return type, so it’s not possible to use return codes. The best way to signal constructor failure is therefore to throw an exception. If you don’t have the option of using exceptions, the “least bad” work-around is to put the object into a “zombie” state by setting an internal status bit so the object acts sort of like it’s dead even though it is technically still alive.

If construction involves RAII then the constructor has the additional responsibility to cleanup any already allocated resources prior to throwing.

Q. How should I handle resources if my constructors may throw exceptions?

A. Every data member inside your object should clean up its own mess.

If a constructor throws an exception, the object’s destructor is not run. If your object has already done something that needs to be undone (such as allocating some memory, opening a file, or locking a semaphore), this “stuff that needs to be undone” must be remembered by a data member inside the object.

As to why std::fstream constructors do not throw exceptions and use the fallback option by default (put the object into a “zombie” state), that is a combination of history and convenience, better explained in the answers to Why are C++ STL iostreams not “exception friendly”?.

dxiv
  • 16,984
  • 2
  • 27
  • 49
  • 1
    As a standard library example that does it this way, consider [`std::lock_guard`](https://en.cppreference.com/w/cpp/thread/lock_guard/lock_guard). – Daniel H Sep 15 '20 at 03:57
  • @DanielH Right. Some container constructors can also throw exceptions like `std::bad_alloc`. And all these cases involve some form of RAII. – dxiv Sep 15 '20 at 04:04