8

Suppose you have a type with a throwing destructor, and a function receiving it by value.
Can that operation ever provide anything better than the basic exception guarantee?
Or formulated differently, may one disregard the destruction of the argument which was passed by value when determining whether an operation has commmit-and-rollback-semantics?

#include <cstdlib>

struct X {
    ~X() noexcept(0) {
        if(rand()%6 == 0) throw 0;
    }
    // some state
};

void update(database db, X arg) noexcept;

X x;
update(db, x);

Personally, based on the definition from wikipedia

  1. Strong exception safety, also known as commit or rollback semantics: Operations can fail, but failed operations are guaranteed to have no side effects, so all data retain their original values

I don't think it's useful to describe update as being strongly exception-safe, even though the function itself cannot even throw.

I would appreciate either someone showing me the folly of my current understanding, or giving a better argument.

7
  • What if a method in the class throws and the caller decides to dispose the object? Will the destructor still close the transaction? Nov 24, 2015 at 16:20
  • Method in which class? And close what transaction? Sorry, but I'm confused now. Nov 24, 2015 at 16:25
  • Well, unless a memento pattern is being used, or a database transaction is involved, I don't see how the state of the class isn't lost when the object is disposed, unless you're saying it's a stateless class. Nov 24, 2015 at 16:45
  • 2
    The question was inspired by the argument here: chat.stackexchange.com/rooms/32049/… chat.stackexchange.com/rooms/32061/… Nov 24, 2015 at 17:50
  • 4
    In general, a throwing destructor is complete poison to all exception safety guarantees. It just gets harder by orders of magnitude. Nov 25, 2015 at 11:29

2 Answers 2

7

I'm not really sure in retrospect, but if update was generic or if X was abstract (ex: cloned and destroyed) -- i.e., if update could not know anything concrete about X, then for all practical purposes I'd describe it as having a strong exception-safety guarantee. At the very least that's as close to a strong exception-safety guarantee you'll ever get in an abstract context without x-raying a data type.

However, X is concrete, so that makes this question a lot more fuzzy. I'm still tempted to say that update "has a strong exception safety guarantee" since it is doing everything it can possibly do to enforce it. Does that sound like crazy talk? It does to me, but I can't really put it much better.

Especially in the context of a team environment where one author implements update and the other implements X, nothing could ever provide a strong exception-safety guarantee since X could be modified in ways that breaks the guarantee in ways completely beyond the control of update. For practical purposes, I think if update does everything it possibly can and should to roll back its own side effects in exceptional paths, it practically provides a strong exception safety guarantee.

Otherwise the strong exception safety guarantee becomes something impractical to ever provide in so many scenarios. Things could change outside the function's control. Things could be abstract in ways beyond the function's knowledge. The whole guarantee would be almost impossible to provide anywhere. In that case might we say that the strong exception guarantee of update propagates a responsibility to X to comply, or is it update that is required to know how X will be implemented at all times? I don't know. This is all very impractical either way, and I don't like impracticalities.

So I'm gonna go with the crude, yeah, "update is exception-safe, but X gone fubar and messed up everything. It's not update's fault at least. Don't sue the author of update! Sue the author of X!". I'd like to describe X as simply being fubar here, and update as possibly providing a strong exception safety guarantee which is being screwed by the fact that X is fubar. That is, unless there is a very hard interface-level understanding that X's destructor is designed to throw, in which case I have no idea how to describe it except as a "big problem". I think we need some fine print and disclaimers to come with these guarantees. update provides a strong exception-safety guarantee provided that [...]. No refunds if this is not the case!

This is like a philosophical question to me, interesting to think about but maybe one with no perfect answer. And what are guarantees anyway? Can we guarantee anything? I might guarantee that drinking beer would make me drunk and that could seem true all the time, but what if an extraterrestrial intervenes and uses some kind of alien technology which makes me sober no matter how much I drink? I can't guarantee that this won't happen. I can only say that this is highly improbable and that if such event were to happen, I would be powerless against it. I did my part. I drank my beer. I'm supposed to get drunk. I strongly guarantee it. But it's not for certain. There's always some probability, however astronomically remote, that the guarantee might not hold true. How about thread safety? Well, what if the alien spaceship comes and zaps our hardware and rearranges instructions in memory such that a formely thread-safe function is now no longer thread-safe at runtime? I can't guarantee that this won't happen. But I will guarantee thread-safety regardless without even going into such fine print about alien space ships, because it makes people feel nice and reassured, and what I'm saying is that I'm doing everything within my power and knowledge to enforce that guarantee.

Descartes once said, "I think therefore, I am." How does he know? What if he isn't? He could be an "am not" but perhaps "nots" can "think". I don't even know if I exist. What does thinking even mean anyway? It's like a word people use to describe some process that appears to go on introspectively and appears as though we, who may not even exist, are making decisions.

What if we are all just bits and bytes on some machine? Like that blonde or brunette in the Matrix. By the way, how can a blonde or especially brunette be stored in such a small stream of characters? Are they like UTF2048? It was some kind of alien stream of characters but there didn't appear to be too many unique characters. Perhaps it's some kind of function call. Anyway, I'm not sure we can guarantee anything. It's all relative to what we believe to be true.

23
  • 5
    "So "exception safety" of any sort and "throwing destructor" should probably never be combined in the same sentence. They are, for all practical purposes, mutually exclusive." +1, can't argue with that logic. If you think you can, you aren't being logical.
    – user22815
    Dec 13, 2015 at 6:37
  • I don't see how this answers the original question. The problem here is that arg will destroy exactly on function exit. The function will have no chance to catch this. If the function has done something, it will not have a chance to roll it back. Dec 20, 2015 at 15:52
  • 2
    Yes, that's the correct conclusion, it doesn't become less correct by being unpleasant. In the case of vector, it's illegal for it's contained type's destructor to ever throw: stackoverflow.com/questions/26902006/…. I understand your "team-oriented" approach, but frankly it's looking at it from a perspective of fairness rather than correctness. Dec 20, 2015 at 16:19
  • 2
    My preferred interpretation is simply that throwing in a destructor is undefined behavior, and what exception guarantees a function has are all based on the implicit assumption that no undefined behavior occurs (indeed, everything in the language makes that assumption). If undefined behavior can include formatting your hard drive, it can certainly include violating the otherwise correct exception guarantees of everything else in the program.
    – Ixrec
    Dec 20, 2015 at 16:48
  • 1
    @Ixrec That may be your preferred interpretation, but throwing from a destructor is not ub in general, that's simply a fact. Dec 21, 2015 at 3:57
-1

Technically speaking, it can perhaps provide the strong exception guarantee. Meaningfully, it can only provide the strong exception guarantee in the trivial case where it does nothing.

Herb Sutter talks about a similar (and more intuitive case) where a function takes an argument by value that has a throwing copy constructor. You can label such a function noexcept. Technically, the exception occurs in the "glue" between the calling code and the called function, not inside the function itself. That's the case here as well. Since the exception is not technically thrown inside update, you could (perhaps) argue that update itself does not generate the error and therefore can offer the strong exception guarantee.

Practically speaking, if update does anything, then calling it (and doing nothing else) can lead to the state of the world changing and an exception being thrown, breaking the strong exception guarantee.

1
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – maple_shaft
    Dec 22, 2015 at 17:16

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.