Valhalla Legends Forums Archive | Advanced Programming | Augmenting C++ with macros

AuthorMessageTime
Arta
One of the few things that C++ lacks that I really liked about Delphi (many years ago) was the try ... finally statement. In pseudocode, it worked like this:

[code]

try
  //do something
  if(something fails)
      break;
 
  //do something else

finally
 
  // tidy up

end;
[/code]

The try block does things, and the finally block always executes: either when execution reaches the end of the try block, or when break is called from the try block. This is useful to avoid this kind of thing:

[code]
// do something
// do something2
if(!something2)
{
  free(something);
}

// do something3
if(!something3)
{
    free(something);
    free(something2);
{

// do something4
if(!something4)
{
    free(something);
    free(something2);
    free(something3);
}
[/code]

Or:

[code]

// do something
if(something)
{
  // do something2
  if(something2)
  {
    // do something3
    if(something3)
    {
        // something4
        if(something4)
        {
          // something 5
        }
        else
        {
          free(something);
          free(something2);
          free(something3);
        }
    }
    else
    {
        free(something);
        free(something2);
    }
  }
  else free(something);
}
[/code]

... both of which are totally ugly and not at all clear. So, I thought, one could do this:

[code]

#define try do
#define finally while(false);

// and then:

try
{
// do something
if(!something) break;

// do something2
if(!something2) break;

// do something3
if(!something3) break;

// do something4
if(!something4) break;

// do something5
}
finally
{
if(something) free(something);
if(something2) free(something2);
if(something3) free(something3);
if(something4) free(something4);
}
[/code]

Much better! Thoughts? Advantages? Disadvantages? Better way to do the same thing? Problems with my logic? :)

The only bad thing I can think of is the obvious and totally correct one: to someone else, it would be confusing and nonstandard. This is entirely true, but I think it can be overcome with good documentation, and I think that this is a missing feature from C++ that would be worth having. It's not like its as bad as the bourne shell or anything :)

Edit: fixed semicolon on finally's #define
May 2, 2005, 2:16 PM
Arta
Just found that there are Microsoft extensions to do this (__try, __finally, __leave). Still, I think it's an interesting question, so I thought I'd leave it up!
May 2, 2005, 2:17 PM
Adron
The only problem with using break to exit is if you are already in some kind of loop inside the try statement. A goto seems safer. You also need to handle exits due to exceptions if you use those.
May 2, 2005, 2:25 PM
Myndfyr
Your macro:
[code]
#define finally while(false)
[/code]
seems like it's pointless to include a finally statement.  As I understand it, the finally statement is to clean up variables you declared within your try block.

If you say while(false), though, it'll never happen, unless you have a label within it and a goto statement.
May 2, 2005, 5:41 PM
Arta
Eh? Those macros end up as:

[code]
do
{
  // try block
} while(false);
{
  // finally block
}
[/code]

The loop executes once, and is only there so you can use break to skip the rest of the try block.

Nah, I never use exceptions. Hate em. Good point about not being able to use loops though - but I like to avoid goto.
May 2, 2005, 6:11 PM
St0rm.iD
WHAT!? Why don't you use exceptions?
May 2, 2005, 7:40 PM
Myndfyr
Ooohh, no semicolon threw me off.  What you've given in your macro definition doesn't include a semicolon.  I think you'd have a compile-time error in the original code you posted.

Also, I'm not sure how VC++'s __try, __catch, __throw, and __finally are meant to work, but at least in VC#, you'd have scope errors if you tried to use do/while and blocking in the way you're trying.  A variable within a try/catch/finally block is in-scope for the entire set; but if you tried to do a do/while, block construction, I *believe* you would have variables out-of-scope immediately following the do/while block, and inaccessible from the block that follows.  The purpose of finally is to clean up resources.

Also, an exception generated within your code would not be trapped, and someone else looking at your try/finally code would wonder what was going on and why your finally block wasn't being executed.
May 2, 2005, 7:48 PM
Arta
[quote author=MyndFyre link=topic=11454.msg110828#msg110828 date=1115063291]
Also, I'm not sure how VC++'s __try, __catch, __throw, and __finally are meant to work, but at least in VC#, you'd have scope errors if you tried to use do/while and blocking in the way you're trying.  A variable within a try/catch/finally block is in-scope for the entire set; but if you tried to do a do/while, block construction, I *believe* you would have variables out-of-scope immediately following the do/while block, and inaccessible from the block that follows.  The purpose of finally is to clean up resources.
[/quote]

Oooh, you're right. Oh well, no matter: MS extensions to the rescue :)
May 2, 2005, 8:31 PM
Arta
[quote author=Banana fanna fo fanna link=topic=11454.msg110823#msg110823 date=1115062832]
WHAT!? Why don't you use exceptions?
[/quote]

Raymond Chen wrote an excellent piece on exceptions. There's another good one at Joel on software. Basically, they just irritate me. Also, try...finally solves a lot of the problems traditionally associated with returning error codes. I don't necessarily want all my error handling to be bunched together, away from the code that might cause the error. If you avoid that by putting try...except blocks around individual things, then you're back with the verbosity that people dislike about returning error codes.

Basically, I think they're a good idea - ideally - but a pain in the arse in practice.
May 2, 2005, 8:37 PM
OnlyMeat
[quote author=Arta[vL] link=topic=11454.msg110791#msg110791 date=1115043405]
[code]
#define try do
#define finally while(false);

// and then:

try
{
// do something
if(!something) break;

// do something2
if(!something2) break;

// do something3
if(!something3) break;

// do something4
if(!something4) break;

// do something5
}
finally
{
if(something) free(something);
if(something2) free(something2);
if(something3) free(something3);
if(something4) free(something4);
}
[/code]
[/quote]

free is a C function not C++, in the C++ world we use delete. C's free/malloc are not to be assumed to work with their respective C++ counterparts new/delete.
May 24, 2005, 9:10 PM
Myndfyr
[quote author=OnlyMeat link=topic=11454.msg113626#msg113626 date=1116969009]
free is a C function not C++, in the C++ world we use delete. C's free/malloc are not to be assumed to work with their respective C++ counterparts new/delete.
[/quote]

C++ is a superset of C, which means that anything that exists in C *should* exist and function the same in C++.

free() is a valid function, and depending on how strictly your compiler enforces casting (it shouldn't be too, too hard), allocating a block of memory and then treating it as an array of structures should work perfectly fine, although yes, new/delete are the superior choices.
May 24, 2005, 9:54 PM
OnlyMeat
[quote author=MyndFyre link=topic=11454.msg113631#msg113631 date=1116971686]
C++ is a superset of C, which means that anything that exists in C *should* exist and function the same in C++.
[/quote]

Give him a gold star for stating the obvious!. You should stick to micky mouse languages ie. c# that don't deal with memory allocation as you clearly know nothing about it.

(1) malloc/free do not invoke an object's constructor/destructor.
(2) new/delete operators can be overloaded. malloc/free can not.
(3) new/delete throws exceptions/invokes new/delete installed exception handlers according to the C++ standard. malloc/free simply return null upon failure which renders the try/catch block pointless for memory allocation failures.

[quote author=MyndFyre link=topic=11454.msg113631#msg113631 date=1116971686]
free() is a valid function, and depending on how strictly your compiler enforces casting (it shouldn't be too, too hard), allocating a block of memory and then treating it as an array of structures should work perfectly fine, although yes, new/delete are the superior choices
[/quote]

Can you read? obviously not, this topic says "Augmenting C++ with macros". Allocating a memory block and using it as an array of structures has nothing to do with C++. C++ is a preprocessor for C which adds object orientated features, and therefore is not C++ specific.

With the C++ new operator you don't deal with casting issues because the allocation is typed.

Once again you have just embarrassed yourself by demonstrating absolutely zero knowledge of C++. Stick to garbage collection languages, you don't have to think to much then.
May 25, 2005, 7:09 AM
Arta
Sigh. I wasn't referring to malloc/free. For someone so pedantic, it's odd that you didn't notice that I'm not malloc'ing anything to be freed  ::)

I was attempting to convey, concisely, that the 'somethings' are resources (mutexes, files, critsecs, memory, whatever) that must be freed after being allocated/requested.

On a side point, although you are correct about the ramifications of using malloc/free instead of new/delete, you are seriously mistaken if you think there is a One True Way to go about doing anything. A programmer, aware of these points, may choose to use malloc/free anyway for other reasons that could be perfectly valid. I don't think you should be so quick to jump down people's throats.
May 25, 2005, 11:34 AM
Kp
[quote author=OnlyMeat link=topic=11454.msg113697#msg113697 date=1117004961](1) malloc/free do not invoke an object's constructor/destructor.
(2) new/delete operators can be overloaded. malloc/free can not.
(3) new/delete throws exceptions/invokes new/delete installed exception handlers according to the C++ standard. malloc/free simply return null upon failure which renders the try/catch block pointless for memory allocation failures.[/quote]
(1) can be useful if you want to allocate all the blocks before spending effort constructing valid data in any of them (consider if construction required expensive effort like multiple non-sequential disk reads or extensive big number computation).
(2) doesn't strike me as particularly useful.  Since you're apparently so knowledgeable about C++'s godly superiority, perhaps you could give some examples of when this is so valuable as to justify mentioning it here?
(3) Given the absolutely pathetic performance of exception handlers in most C++ implementations, the traditional approach of returning NULL is sometimes more desirable.  For maximum compactness, disabling exception support (and thereby omitting all associated tables and control information) can yield quite a savings, at a cost of supporting exceptions from new/delete.  malloc/realloc still work fine in such a case.
(4) C++ lacks any equivalent to realloc, which guarantees that growing an array will require copying; using malloc gives the possibility of an in-place growth, depending on the intelligence of your memory manager and your recent patterns of allocation/release.

[quote author=OnlyMeat link=topic=11454.msg113697#msg113697 date=1117004961]C++ is a preprocessor for C which adds object orientated features, and therefore is not C++ specific.[/quote]Most C++ implementations have been free-standing compilers for quite a while.  The optimizing and code emission components almost certainly share code with strict C (and possibly with a Java backend, a Fortran backend, etc. as well), but C++ code isn't just rewritten as C code and then compiled.

[quote author=OnlyMeat link=topic=11454.msg113697#msg113697 date=1117004961]With the C++ new operator you don't deal with casting issues because the allocation is typed.[/quote]Contrary to your apparent beliefs, casting is not the end of the world.  Type checking is intended to help the author avoid stupid mistakes, not to build a rigid cage which he must never leave.  How do you use mmap() (or its Win32 equivalent CreateFileMapping + MapViewOfFile)?  mmap returns a void*, and IIRC MapViewOfFile also returns a type-neutral pointer, so you must cast the results to be able to read it.  I'm sure there're other examples where casting is unavoidable if you want to interact with the system.
May 25, 2005, 2:38 PM
Arta
[quote author=OnlyMeat link=topic=11454.msg113697#msg113697 date=1117004961]
C++ is a preprocessor for C which adds object orientated features, and therefore is not C++ specific.
[/quote]

...can't believe I missed that little nugget of gold. This hasn't been true for a long time. The first C++ implementation - as implemented by stroustrup - was indeed a C preprocessor. No modern compiler that I know of is, however.
May 25, 2005, 3:37 PM
Eibro
If you were to dig deeper into the language features, I don't think you would be having these problems. Ever hear of Resource Acquisition Is Initialization, or the Scoped Lock idiom?

Wouldn't it be cool if you could do this (or something similar)?

typedef basic_auto_handle< HANDLE, CloseHandle > auto_handle;

void fn() {

  auto_handle h = CreateFile( ... );
  auto_handle h2 = CreateFileMapping( ... );
}

Now the destructors of h and h2 will clean anything up that needs to be cleaned. Even in the face of exceptions, resources will still be freed. I don't agree with what you cited: http://www.joelonsoftware.com/items/2003/10/13.html. If you're writing exception safe code from the start, it doesn't matter how many possible "invisible" exit points there are. See Herb Sutter's Exceptional C++, and More Exceptional C++ for some in depth examples of writing exception safe code.
May 25, 2005, 3:43 PM
Arta
Ooh, that is nice. I'll have to have a play. I still don't like exceptions much though :P
May 25, 2005, 4:34 PM
tA-Kane
[quote author=Arta[vL] link=topic=11454.msg110791#msg110791 date=1115043405]
[code]
// do something
// do something2
if(!something2)
{
free(something);
}

// do something3
if(!something3)
{
free(something);
free(something2);
{

// do something4
if(!something4)
{
free(something);
free(something2);
free(something3);
}
[/code]

Or:

[code]

// do something
if(something)
{
// do something2
if(something2)
{
// do something3
if(something3)
{
// something4
if(something4)
{
// something 5
}
else
{
free(something);
free(something2);
free(something3);
}
}
else
{
free(something);
free(something2);
}
}
else free(something);
}
[/code][/quote]I completely agree with Adron. Too many people underestimate the value of the goto keyword. And in fact, a lot of common new languages don't even provide one, which is quite frustrating to me. *cough*PHP? Java?*cough* It's one of the reasons I quite dislike web programming.
[quote author=Adron link=topic=11454.msg110794#msg110794 date=1115043931]A goto seems safer.[/quote]

In any case, here's how I would do what you're asking, which looks nice in my opinion:
[code]
//do something
if (!something) goto finally;

//do something2
if (!something2) goto finally;

// do something3
if (!something3) goto finally;

// do something4
if (!something4) goto finally;


finally:
if (something) free something;
if (something2) free something2;
if (something3) free something3;
if (something4) free something4;
[/code]
May 25, 2005, 9:50 PM
Arta
Yes, this is the one acceptable use of goto, imho :)

Nonetheless, I still don't exactly like goto, and a more structured way to do this would be nice. Perhaps I should email Bjarne Stroustrup :)
May 30, 2005, 5:20 PM
Kp
What about this?

[code]
act1;
if (ok(a1)) {
    act2;
    if (ok(a2)) {
        act3;
        if (ok(a3)) {
            return; /* or goto out */
        } /* assume failure of act3 means it doesn't need to be freed */
        free(act2);
    }
    free(act1);
}[/code]
May 30, 2005, 5:24 PM

Search