C++ Exception Safety – Achieving Strong Exception Safety with Pass-by-Value Arguments

cerror handlingexception handlingexceptions

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.

Best Answer

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.