5

As far as I understand RAII refers to acquire resources in ctor and release them in dtor.

Ctor acquires some resources and can fail, resulting in an exception. Dtor releases the resources and can also fail, but exception from dtors are foobar so no exception.

class A {
  A() throw(Ex) { // acquire resources }
  ~A() throw() { // release resources }
}

So if the user of class A should be made aware of an error happening in A′s uninitialisation I can outsource uninitialisation to a function that throws, called from a dtor that swallows exceptions:

class A {
  A() throw(Ex) { // acquire resources }
  ~A() throw() { try {Release(); } catch(...) {} }

  void Release() throw(Ex) { // release resources }
}

That way the user can call Exit() if he wants feedback for release-error or just ignore by letting dtor do it′s job when A goes out of scope (e.g. some other exception occurs where A is used).

To prevent multiple executions of Exit() (first explicitly from user, later indirect by dtor) I have to add an init-status:

class A {
  bool init;
  A() throw(Ex) { init = true; // acquire resources }
  ~A() throw() { try {Release(); } catch(...) {} }

  void Release() throw(Ex) {
    if(!init) return;
    init = false;
    // release resources
   }
}

Are there better ways to do this or do I have to implement that pattern every time resource-release can fail and I want to know about it?

5
  • 3
    As a rule of thumb: Don't throw exceptions from destructor functions! Commented May 18, 2014 at 12:35
  • 2
    The upshot is that RAII doesn't handle errors in the release process. Thus you should a pick a sensible default behaviour for a non-throwing release, and add an explicit error handling mechanism for when it's desired. std::fstream does this.
    – Kerrek SB
    Commented May 18, 2014 at 12:45
  • Do you not use exception specifications other than specifying that a function doesn't throw (and that would probably use noexcept or noexcept(true)). Commented May 18, 2014 at 12:46
  • In that case with RAII the error handling mechanism always has to be placed in the class itself rather than where it is used?
    – downforme
    Commented May 18, 2014 at 12:52
  • I´m aware that the compiler ignores the throw(Ex) clauses, it´s just to explain the classes behavior
    – downforme
    Commented May 18, 2014 at 12:56

2 Answers 2

4

Releasing resources shouldn't have any scope to fail. For example, releasing memory can certainly be implemented in a form which doesn't throw an exception. RAII is intended to clean-up resources and not to deal with error resulting from substantial clean-up.

Clearly, there are clean-up operations which could fail. For example, closing a file could fail, e.g., because closing it will flush the internal buffer and that may fail because the disc the file is writing to is full. If the clean-up operation fails, there should probably be a suitable release operation and if users are interested in reporting errors from the clean-up, they should use this method: in the normal path, there would be an opportunity to handle any error.

When the release is made as part of handling an existing error, i.e., an exception is thrown and the release operation isn't reached, any exceptions will need to be eaten by the destructor. There may be some handling method to, e.g., log the message of the exception thrown but the exception shouldn't escape the destructor.

4 Comments

Enter at least 15 characters
1. Releasing resources does have scope to fail, at times. You use some 3rd party library, and it has foo_status release_foo(foo_handle_type fh). What are you going to do? 2. The destructor cannot reasonably be expected to handle things like logging. The applications knowns how and where to log things, not some RAII resource-holder class (which may well by non-application-specific).
Enter at least 15 characters
Enter at least 15 characters
What a condescending answer. Resource freeing can fail as much as resource allocation.
Enter at least 15 characters
Enter at least 15 characters
@einpoklum Encapsulate it in a real resource type to get any errors being asserted and absent at runtime (which is the common practice to deal with system APIs) during the release, or ignore the errors on release as a fault-tolerant way handling the buggy 3rd party library, or just keep them away as a type of resource in the RAII sense. (The failure during cleanup is not in the scope of RAII, but it can live outside the destructor, e.g. fstream::close. The failbit handling is the exact example to have another path of failure in spite of RAII.)
Enter at least 15 characters
Enter at least 15 characters
@Enerccio Almost nonsense of ill-formed abstractions. See the comments in the other answer.
Enter at least 15 characters
Enter at least 15 characters
Enter at least 15 characters
2

Overall, I think if you keep following the RAII guidelines, then you definitely need throw an exception within a destructor.

For example: closing a file, releasing a mutex, closing a socket connection, Unmap a file map, closing a communication port, Rollback a db trancaction, and etc. Too much work done in the RAII destruction will fail.

What should we do with there failures? In the RAII destructor, we almost never have enough information to know how to handle these failures properly. We can only choose to ignore it or pass it to the up level.

But if these errors can be safely ignored, why do the APIs provided by the operating system, such as close, munmap, pthread_mutex_destroy and many more all return an error code to us? Could they simply return void?

So we end up having to write a destructor like this:

CResource::~CResource() noexcept(false)
{
    if (-1 == close(m_fd))
    {
        // ...
        if (std::uncaught_exception())
        {
            return;
        }
        throw myExp(m_fd, ...);
    }
    // ...
}

Of course, in addition to throwing exceptions, we can also choose our own upward propagation method. For example, let the upper component register a callback method for each type that may throw when destructing, or maintain a global queue to store and pass these exceptions, and so on.

But it is clear that these alternatives are more clumsy and difficult to use. This is equivalent to re-implementing an exception mechanism yourself.

9 Comments

Enter at least 15 characters
std::uncaught_exception() is a bad idea. At least use std::uncaught_exceptions(). Mind the s.
Enter at least 15 characters
Enter at least 15 characters
@Deduplicator Thanks, but frankly I have not found any additional advantages in using the s version in our scenario?
Enter at least 15 characters
Enter at least 15 characters
The problem is that you cannot differentiate between being used while an exception is in flight, and being called due to an exception causing stack-unwinding.
Enter at least 15 characters
Enter at least 15 characters
@Deduplicator If I understand you correctly, you means some destructors may temporarily construct some objects, and the destructors of these temporary objects can still throw exceptions right? It may be useful, but I don't think it is a good idea to construct a new object in a destructor.
Enter at least 15 characters
Enter at least 15 characters
It's a lot safer to create objects in a destructor, than throw from it. Have you ever logged anything in a destructor? That also creates temporary objects, depending on how you log. Much of the standard library depends on destructors not throwing, ever, because it breaks commit-or-rollback.
Enter at least 15 characters
Enter at least 15 characters
|
Enter at least 15 characters

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.