4

When writing exception safe code, it is necessary to consider the exception safety guarantee (none, basic, strong or no-throw) of all the functions called. Since the compiler offers no help, I was thinking that a function naming convention might be helpful here. Is there any kind of established notational standard indicating the level of exception safety guarantee offered by functions? I was thinking along the lines of something hungarian-like:

void setFooB(Foo const& s); // B, offers basic guarantee
int computeSomethingS();    // S, offers strong guarantee
int getDataNT() throws();   // NT, offers no-throw
void allBetsAreOffN();      // N, offers no guarantee

Edit: I agree with comments that this kind of naming convention is ugly, so allow me to elaborate on my reasons for suggesting in.

Say I refactor some code, and in that process, change the level of exception safety offered by a function. If the guarantee has changed from, say, strong to basic (justified perhaps by improvement in speed), then every function that calls the refactored function must be reconsidered for their exception safety. If the change in guarantee triggered a change in the function name as well, it would allow the compiler to help me out a little bit in at least flagging all uses of the changed function. This was my rationale for suggesting the naming convention above, problematic as it is. This is very similar to const, where a change in the const-ness of a function has cascading effects on other calling functions, but in that situation the compiler gives very effective assistance.

So I guess my question is, what kind of work habits have people developed in order to ensure that code actually fullfills their intended exception guarantees, especially during code maintenance and refactoring.

  • 4
    please, there is no need for another code wart system, didn't we have enough? – Gene Bushuyev Aug 13 '11 at 20:00
  • What do you mean by "exception-safe code"? There's no need to ensure that no function ever throws, and all your standard containers may throw habitually anyway ... usually you'd identify the places in your program flow where you can sensibly deal with exceptions and handle those that you can deal with (rethrowing all others), *without* worrying where each individual one came from -- otherwise you're just back to C-style error handling... – Kerrek SB Aug 13 '11 at 20:17
  • Don't do that, as Gene suggested. May be you can put them in separate namespaces, back them with some template or default arguments. Better to put them in separate headers/namespaces and document well. – Ajay Aug 13 '11 at 20:36
  • 1
    Everything should offer at least the basic guarantee, so you don't have to worry about none. Document (via comments or good, meaningful names) things that offer a stronger guarantee; in my experience these are not that common. Consider using `throws()` or `noexcept ` to make the no-throw guarantee explicit (but also look into the downsides of `throws()`). Please don't use horrible name decorations for the purpose. – Alan Stokes Aug 13 '11 at 21:29
  • 1
    @Kerrek SB: Exception guarantees are not about stopping exceptions they are about what you guarantee about an object when an exception is thrown. Most objects should probably provide the strong guarantee (like the STL containers) (it works or an exception is thrown with no change to the object), but sometimes all you need is the basic guarantee (the object is not left in an invalid state). Very few methods require the no-throw guarantee (apart from destructors and swap()) and you should be smart enough to never write code that provides no guarantee. – Martin York Aug 14 '11 at 00:25
  • @Kerrek: see http://stackoverflow.com/questions/106586/what-are-the-principles-guiding-your-exception-handling-policy/106749#106749 – Martin York Aug 14 '11 at 00:48
  • @Martin: Yeah, I know about the guarantee levels, but how would you use this fine-grained information? Suppose you're calling a method. Somewhere else you catch the exception. Chances are your object has already been destroyed during the unwinding. What's a good use case for that extra information? – Kerrek SB Aug 14 '11 at 01:06
  • @Kerrek: Fair enough. I agree with you on that point. – Martin York Aug 14 '11 at 01:09
  • Maybe the OP can add a tiny paragraph on the rationale for this... – Kerrek SB Aug 14 '11 at 01:12
  • @Kerrek SB: say you need to build a transaction system, how do you order the method calls on your object so that you can get the Strong Guarantee at the lower cost ? It depends on their guarantees. Knowing a function guarantee is worthy... but the naming is insane. – Matthieu M. Aug 14 '11 at 13:05
  • @Kerrek SB: I think that the point is not about what happens up the call hierarchy when the exception is caught, but rather about what you need to care about while writing exception safe code. You can ensure that this assignment offers the strong guarantee: `type& operator=( type t ) { swap( *this, t ); }` only if you know that `swap` itself is *no-throw*. I have not thought too much about what the extra information on the different sets of guarantees can yield (i.e. whether only *no-throw* is of any use or knowledge of the other levels help writting code) – David Rodríguez - dribeas Aug 14 '11 at 14:37
  • i.e. If a function calls two other functions that both offer the *strong* exception safety guarantee, there is very few to say about the exception guarantees of the overall operation) – David Rodríguez - dribeas Aug 14 '11 at 14:40
  • @David: Well, yes, nothrow is fine and useful and can be propagated. Similarly, if you have the strong guarantee, you know that you have "atomic" behaviour. But what use is the basic guarantee? Matthieu's idea about a transaction system is interesting, but in general, what do you do with an object that's "valid but indeterminate" other than destroy or overwrite it? – Kerrek SB Aug 14 '11 at 14:41

4 Answers4

5

I usually find myself documenting that in comments (doxygen), excepts the no throw guarantee, that I often tag with the throw() exception specification if and only when sure that the function is guaranteed not to throw, and exception safety is important.

That is, I usually worry more about exceptions in parts of the code where an unhandled exception would cause problems, and deal with that locally (ensure that your code is exception safe by other means, as RAII, performing the work outside and then merging the results with a no throw operation --i.e. no throw swap, which is about the only function that I actively mark as throw().

Other people might have other experiences, but I find that to be sufficient for my daily work.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
3

I don't think you need to do anything special.

The only ones I really document are no-throw and that is because the syntax of the language allows it.

There should be no code in your project that provides no guarantee. So that only leaves strong/basic to document. For these I don't think you need to explicitly call it out as it not really about the methods themselves but the class as a whole (for these two guarantees). The guarantees they provide are really dependent on usage.

I would like to think I provide the strong guarantee on everything (but I don't) sometimes it is too expensive sometimes its just not worth the effort (if things are going to throw they will be destroyed anyway).

Martin York
  • 257,169
  • 86
  • 333
  • 562
1

I am thinking about adding

@par Exception Safety

Strong guarantee

to my javadocs where appropriate.

Vinnie Falco
  • 5,173
  • 28
  • 43
1

I understand your willingness to do well, but I am unsure about such a naming convention.

I am, in general, wary of naming conventions that are not enforced by the language: they are prone to become the greatest liars.

If you truly need such things, my suggestion is to get your hands on a compiler (Clang for example) and add a new set of attributes. Do note that you'll need to edit your Standard Library provided headers, and all 3rd party headers you rely on, to annotate them so that you can get those guarantees from the ground up.

Then you can have the compiler check the annotations (won't be trivial either...), and then the annotations become useful, because they cannot lie.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722