tags:

views:

201

answers:

6

It's often needed to accomplish the following task: change the state of something, do action, then change the state back to original. For example, in Win32 GDI it's needed to change background color, then do some drawing, then change the color back.

It can be either done directly:

COLORREF oldColor = SetBkColor( deviceContext, newColor );
drawStuff( deviceContext );
SetBkColor( deviceContext, oldColor );

or via a bracket class that would do the forward change in the constructor and the backward change in the destructor:

CBkColorSwitcher switcher( deviceContext, newColor );
drawStuff( deviceContext );
//once control reaches end of block the switcher is destroyed and the change is reverted

The advantage of a bracket class is obvious - if an exception is thrown in between the changes the change is reverted correctly. What are the disadvantages?

+2  A: 

I consider this a best practice. I don't see any disadvantages (except maybe readability?)

jeroenh
+12  A: 

This is in fact a well known and widely used C++ idiom known as RAII. Win32 APIs are C APIs and their implementation patterns are different. If you are programming in C++, it is better to handle resource allocation and deallocation using the RAII idiom, by writing thin wrappers on the C API or better re-use existing, well-designed C++ replacements. Java programmers can look upon RAII as a replacement for the finally clause.

Vijay Mathew
Java programmers can think of the finally clause as a technical kludge to mimic the effects of the more elegant RAII technique. finally moves responsibility from the designer to the user which leads to more potential errors as the user needs to be as skilled as the designer to be correct. With RAII the user is protected and the responsibility of correctness falls on the designer.
Martin York
+1  A: 

One disadvantage is that you usually actually have to define a new class which tends to be a bit of an overhead.

That aside it's (another) very common RAII example and is, in general, a very good approach.

Edit: If, instead of writing a class you just have a function, you can use shared_ptr to execute an arbritary code block on exit from a scope. I think it's probably a bit too cute for most applications though.

Charles Bailey
Do you mean using the RAII properties of the shared_ptr? Can you elaborate on that?
xtofl
Look at the link. `shared_ptr` can take a custom deleter. You can (ab)use it (and perhaps bind) to "delete" a void pointer by executing an arbritary function. On leaving a block scope, the custom deleter will be called.
Charles Bailey
Alexandrescu's ScopeGuard<> may be a slightly more readable and expressive alternative
Phil Nash
@Phil Nash: I agree, I was trying to find an example of something the countered the "writing a new class is a fair amount of overhead" point and `shared_ptr` abuse sprang to mind first.
Charles Bailey
`shared_ptr` with custom deleter is usually used as a way to manage handles as such. To execute arbitrary code, `ScopeGuard` is indeed a better suited option.
Pavel Minaev
+4  A: 

A couple of disadvantages, you need to write more code, you end up creating more objects. You have no control over the use of it. Use it wrong and you lose the benefits. e.g.

CBkColorSwitcher * switcher = new CBkColorSwitcher(......)

That said, I think the advantages far outweigh the disadvantages and would prefer the bracket class approach

Glen
The extra effort spent on writing the C++ wrappers are paid off in terms of less number of bugs created by the more verbose and error prone C code.
Vijay Mathew
I disagree with having to write more code. More braces, yes. But the number of set/restore lines are reduced by a factor 2 in the final code.
xtofl
It's often not more code. If you have to do the same thing more than a couple of times, being able to reuse the class saves code. It also ensures that your change gets reverted if an exception is thrown. Without it, you'd have to write a try/catch just to revert to the initial state.
jalf
@xtofl You have to write more code because you have to create the class, add a constructor, destructor, etc. Using it results in less code in you method where you're doing the work, but it's another class you have to maintain, hence more code. Like I said though, it's worth the overhead.
Glen
@Vijay I agree about the usefulness of RAII but the OP didn't ask if RAII was better or not, they asked what are any possible disadvantages to using it
Glen
Yes, I agree you answered the question well. I just pointed out how even that 'disadvantage' can in the long run, turn beneficial.
Vijay Mathew
@Glen: jalf points out your assertion of more code is wrong. You add 6 lines for a class with a constructor/destructor. Then subtract 1 line for each use. Thus as long as it is used 6 time or more no extra code.
Martin York
@Glen: Using something incorrectly will always loose any advantages. Just like forgetting to use the C functions in pairs. So this is not a real reason. Note using NEW like that is always wrong (exception safety). in modern C++ pointers are always wrapped in a smart pointer.
Martin York
+2  A: 

The disadvantages I see are that (at least in the example you gave):

  • It is less clear as to what you are actually doing unless the reader of the code understands the responsibility of the bracket class.
  • The cost of creating the bracket class
  • Writing the code to implement the bracket class will take longer than a version without it.

Having said this, using RAII is the "correct" way to do this and the benefits far outweigh the disadvantages.

RC
+1  A: 

I'm nitpicking here, but:

  1. Code size, your code will be bigger because of the exception handler.
  2. You need to write a lot of class to handle all sort of switches.
  3. Bigger stack.
  4. Always preforming code on all exceptions, even if its not need (for example you just want the application to crash)
Shay Erlichmen
which exception handler? The stack size is pretty insignificant. An object with no state will typically take only one byte.
jalf
@jalf I'm talking about the code that the compiler inserts in order for the dtor to be called no matter what. as for your other comment, as I said I'm nitpicking and anyway I don't know how you come up with that object with no state takes a single byte.
Shay Erlichmen
Technically an object should have an address even if its size 0 bytes, however if nobody takes its address a compiler can (and will) not perform any instruction to allocate storage for it and just call the destructor (or even add inline code for the destructor) at the points where the 'object' would be destroyed.
Charles Bailey
In C/C++, an object cannot have a size of 0 bytes, by definition of "object" in the respective specifications. I.e. for no type T, `sizeof(T)` can ever be 0. Of course, if no-one actually observes that size in any direct or indirect way, the compiler can optimize as described by Charles. In practice, all C++ compilers I know will optimize a typical stateless scope guard class into a pair of constructor and destructor calls (and often inline them as well).
Pavel Minaev