• Welcome to Valhalla Legends Archive.
 

Augmenting C++ with macros

Started by Arta, May 02, 2005, 09:16 AM

Previous topic - Next topic

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:



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

finally
 
  // tidy up

end;


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:


// 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);
}


Or:



// 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);
}


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



#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);
}


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

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!

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.

MyndFyre

Your macro:

#define finally while(false)

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.
QuoteEvery generation of humans believed it had all the answers it needed, except for a few mysteries they assumed would be solved at any moment. And they all believed their ancestors were simplistic and deluded. What are the odds that you are the first generation of humans who will understand reality?

After 3 years, it's on the horizon.  The new JinxBot, and BN#, the managed Battle.net Client library.

Quote from: chyea on January 16, 2009, 05:05 PM
You've just located global warming.

Arta

Eh? Those macros end up as:


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


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.

Banana fanna fo fanna

WHAT!? Why don't you use exceptions?

MyndFyre

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.
QuoteEvery generation of humans believed it had all the answers it needed, except for a few mysteries they assumed would be solved at any moment. And they all believed their ancestors were simplistic and deluded. What are the odds that you are the first generation of humans who will understand reality?

After 3 years, it's on the horizon.  The new JinxBot, and BN#, the managed Battle.net Client library.

Quote from: chyea on January 16, 2009, 05:05 PM
You've just located global warming.

Arta

Quote from: MyndFyre on May 02, 2005, 02:48 PM
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.

Oooh, you're right. Oh well, no matter: MS extensions to the rescue :)

Arta

Quote from: Banana fanna fo fanna on May 02, 2005, 02:40 PM
WHAT!? Why don't you use exceptions?

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.

OnlyMeat

Quote from: Arta[vL] on May 02, 2005, 09:16 AM

#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);
}


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.

MyndFyre

Quote from: OnlyMeat on May 24, 2005, 04:10 PM
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.

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.
QuoteEvery generation of humans believed it had all the answers it needed, except for a few mysteries they assumed would be solved at any moment. And they all believed their ancestors were simplistic and deluded. What are the odds that you are the first generation of humans who will understand reality?

After 3 years, it's on the horizon.  The new JinxBot, and BN#, the managed Battle.net Client library.

Quote from: chyea on January 16, 2009, 05:05 PM
You've just located global warming.

OnlyMeat

Quote from: MyndFyre on May 24, 2005, 04:54 PM
C++ is a superset of C, which means that anything that exists in C *should* exist and function the same in C++.

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 from: MyndFyre on May 24, 2005, 04:54 PM
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

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.

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.

Kp

Quote from: OnlyMeat on May 25, 2005, 02:09 AM(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.
(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 from: OnlyMeat on May 25, 2005, 02:09 AMC++ is a preprocessor for C which adds object orientated features, and therefore is not C++ specific.
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 from: OnlyMeat on May 25, 2005, 02:09 AMWith the C++ new operator you don't deal with casting issues because the allocation is typed.
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.
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

Arta

Quote from: OnlyMeat on May 25, 2005, 02:09 AM
C++ is a preprocessor for C which adds object orientated features, and therefore is not C++ specific.

...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.