4

Should scoped objects (with complimentary logic implemented in constructor and destructor) only be used for resource cleanup (RAII)?

Or can I use it to implement certain aspects of the application's logic?

A while ago I asked about Function hooking in C++. It turns out that Bjarne addressed this problem and the solution he proposes is to create a proxy object that implements operator-> and allocates a scoped object there. The "before" and "after" are implemented in the scoped object's constructor and destructor respectively.

The problem is that destructors should not throw. So you have to wrap the destructor in a try { /* ... */ } catch(...) { /*empty*/ } block. This severely limits the ability to handle errors in the "after" code.

Should scoped objects only be used to cleanup resources or can I use it for more than that? Where do I draw the line?

Community
  • 1
  • 1
StackedCrooked
  • 34,653
  • 44
  • 154
  • 278
  • 1
    I don't think it's abusive to do that, but isn't something like [this](http://stackoverflow.com/questions/5081123/how-to-add-code-at-the-entry-of-every-function/5081272#5081272) cleaner? – Flexo Oct 26 '11 at 18:50
  • @awoodland that's quite nice and makes C++11 even more attractive. – StackedCrooked Oct 26 '11 at 18:56
  • 2
    I think using a destructor doesn't count as RAII. Nothing in your question counts as RAII. Your real question is, is that function hooking methodology bad practice? – Mooing Duck Oct 26 '11 at 18:58
  • 1
    This smells like Aspect Oriented Programming. – MartyTPS Oct 26 '11 at 19:58

3 Answers3

4

If you pedantically consider the definition of RAII, anything you do using scoping rules and destructor invocation that doesn't involve resource deallocation simply isn't RAII.

But, who cares? Maybe what you're really trying to ask is,

I want X to happen every time I leave function Y. Is it abusive to use the same scoping rules and destructor invocation that RAII uses in C++ if X isn't resource deallocation?

I say, no. Scratch that, I say heck no. In fact, from a code clarity point of view, it might be better to use destructor calls to execute a block of code if you have multiple return points or possibly exceptions. I would document the fact that your object is doing something non-obvious on destruction, but this can be a simple comment at the point of instantiation.

Where do you draw the line? I think the KISS principle can guide you here. You could probably write your entire program in the body of a destructor, but that would be abusive. Your Spidey Sense will tell you that is a Bad Idea, anyway. Keep your code as simple as possible, but not simpler. If the most natural way to express certain functionality is in the body of a destructor, then express it in the body of a destructor.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • 1
    More to the point, what is a resource, fundamentally? I would aver that a resource isn't so much a 'thing' as a responsibility. In practice, a resource held by X usually represents something which some entity is doing on behalf of X and will keep doing unless told to stop (e.g. a memory-manager may be reserving a block of memory for X, or the OS may be keeping a file open for X, etc.) but that's an implementation detail. More fundamentally, if X holds a resource, that means X holds the responsibility of ensuring that some particular action gets carried out before it's abandoned. – supercat Oct 26 '11 at 20:37
2

You want a scenario where, guaranteed, the suffix is always done. That sounds exactly like the job of RAII to me. I however would not necessarily actually write it that way. I'd rather use a method chain of templated member functions.

Puppy
  • 144,682
  • 38
  • 256
  • 465
1

I think with C++11 you can semi-safely allow the suffix() call to throw. The strict rule isn't "never throw from a destructor", although that's good advice, instead the rule is:

never throw an exception from a destructor while processing another exception

In the destructor you can now use std::current_exception, which I think verifies the "while processing another exception" element of the destructor+exception rule. With this you could do:

~Call_proxy() {
    if (std::current_exception()) {
        try {
            suffix();
        }
        catch(...) {
          // Not good, but not fatal perhaps?
          // Just don't rethrow and you're ok
        }
    }
    else {
        suffix();
    }
}

I'm not sure if that's actually a good idea in practise though, you have a hard problem to deal with if it throws during the throwing of another exception still.

As for the "is it abuse" I don't think it's abusive anymore than metaprogramming or writing a?b:c instead of a full blown if statement if it's the right tool for the job! It's not subverting any language rules, simply exploiting them, within the letter of the law. The real issue is the predictability of the behaviour to readers unfamiliar with the code and the long term maintainability, but that's an issue for all designs.

Flexo
  • 87,323
  • 22
  • 191
  • 272