The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

Resurrection of C++ Macros

We all know that C and C++ macros are evil, sharing a level of hell with the devil himself.

But do you find that they have a place in your code.  I was writing a collection of different classes that were identically structured.

I found I could make macros which used its black magic to raise the verbose implementation from but a single line of code.

It seems like this method reduces typing and makes changes to the classes happen only in one place.  But it can clobber unsuspecting code which includes this miserable macro (though only massocists would have a variable with a similar name, and they like tourture, so wheres the harm).

So macros to generate repetative code, like Jesus or the Devil?

Sunday, April 08, 2007
 
 
I'd argue that the use of templates is superior, because they are often more easily debugged (rare is the source-level debugger that will let you single step though a macro, but the equivalent functionality wrt templates is common) and the error messages are less cryptic (yes, less cryptic than templates, I know).

As for macros vs Jesus, I'll take macros; Jesus might give me life eternal, but without macros, I'll be spending all of it typing.
Tom_
Sunday, April 08, 2007
 
 
s/less cryptic/more cryptic/
Tom_
Sunday, April 08, 2007
 
 
Almost always damnable, IMO.

If you find yourself creating lots of similar classes, there are other mechanisms to try before resorting to macros. Templates is one. Inheritance/composition is another. Even interface/implementation separation may suffice. Or a virtual or inline function. Or even a nice mixture of these.

Only if all the above fails should you start considering macros. Particularly macros included in headers (every architectural evil in C++ is worse when included in headers rather than the code, as often it gets re-included in all sorts of other places that someone else won't expect.)

Even then I'd consider repetition the lesser evil than the macro in many cases. If you must have it, give it a nice long name to reduce the chance that it will tromp some other innocent piece of code.
Duncan Sharpe
Sunday, April 08, 2007
 
 
> But do you find that they have a place in your code.

3 places:

1) As include guards in header files
2) To define OS-specific primitive types (when defining cross-platform binary-compatible data structures)
3) To conditionally-include/exclude platform-specific and/or built-target-specific code (including "__declspec import" and similar).
Christopher Wells Send private email
Sunday, April 08, 2007
 
 
To absolve myself of my stupid earlier post (maybe the moderators deleted it already -- though I thought it _somewhat_ amusing) a good rule of thumb for the use of macros is this: does the expansion use the # or ## operators? If it does not, some other mechanism will probably (though not always!) suffice.

One shouldn't be too quick to jump straight in with # and ##, even when they seem like just the ticket (it's worth spending some time thinking about how to avoid them if you can), but when you need them, you really do.

I'd hesitate to condemn macros as universally evil, but they do have a nice set of pitfalls. And they rarely play well with the debugger; to stay on-topic, many consider the debugger as the tool of the devil, but for certain classes of tricky bug it's hard to beat, and when you're in that situation you probably won't be too happy having to do the extra work to find out what's *really* going on.
Tom_
Sunday, April 08, 2007
 
 
__FILE__ and __LINE can be useful in log messages.
Christopher Wells Send private email
Sunday, April 08, 2007
 
 
I agree with Christopher on two out of three - I use them for include guards, and for changing platform-specific syntax such as declspec declarations to a codeword. You use macros for these things because nothing else works.

For defining cross-platform types, it's sufficient to #define the operating system itself (usually this is done for you anyway), and use #ifdef etc to choose a suitable typedef. This way you avoid any additional #define's.

Note that both of these uses avoid the macro function, which is the greater of the two #define evils.

Along with this I (along with most everybody, I suspect) use an ALLCAPS naming convention just for macros, so that you can easily visually distinguish it.

Even with this limited usage, I've still had trouble once or twice. For example, it's easy sometimes to copy a class definition out of a header file with the intent of modifying and reusing it. But woe betide you if you copy and paste the header guards as well! Suddenly you have two header files that sometimes work, and sometimes don't, in a highly mysterious fashion.
Duncan Sharpe
Sunday, April 08, 2007
 
 
One of my favorite past times!  Chasing down some macro magic due to inclusion from some file I have never heard of deep in the trunk of some source tree.

I'm not knocking macros or your use of them.  They are included in the language and can be used elegantly or bastardized in the most perverse sense of abuse; Just like any other part of the language.  My only advice is don't get clever for the sole purpose of being clever.  With multi-paradigm support for programming it stands to reason that you have considerable options while designing.  I always look for alternatives when I ask myself a morality question as opposed to a legality based situation; That's my rule of thumb for a red flag and the potential source for code smell. 

Sometime you must ignore the red flag which is why I have no answer on this subject but this is my experience concerning it.
Chris Send private email
Sunday, April 08, 2007
 
 
P.S.  When I say I am not knocking macros I am speaking in this context:

http://cpptips.hyperformix.com/cpptips/evil_macros.txt

Not sure my playful sarcasm came through - while respecting you may very well have a real need to use them in some situation I am unfamiliar.
Chris Send private email
Sunday, April 08, 2007
 
 
My rule of thumb is - prefer another method if it works, but don't be afraid of them if it simplifies your code.

The last time I created a macro, I used it to simplify a command table implementation. The setup is a little difficult to follow, but the implementation of a command becomes dirt simple. The main benefit is that there's no way for the table and the class implementation to become out of sync. It looks something like this, where MyClass is the class that implements all of the commands, AddCommand is a static member function to extend the static command table, and CreateCommand is a helper class:

// define a pointer to a command member function
typedef void (MyClass::*CommandMember)();

struct CreateCommand
{
  CreateCommand(const char * commandName, CommandMember commandPtr)
  {
      MyClass::AddCommand(commandName, commandPtr);
  }
};

#define COMMAND(name) \
  static CreateCommand cmd##name(#name, &MyClass::Cmd##name); \
  void MyClass::Cmd##name()

COMMAND(DoSomething)
{
  ...
}

I don't have the code at hand, please forgive me if I messed up the exact syntax.
Mark Ransom Send private email
Monday, April 09, 2007
 
 
> I found I could make macros which used its black magic

Black magik turns your soul. The only time I ever use macros is for logging. I had a particularly exciting recent experience where I couldn't understand a new library. I thought they were using some deep black C++ magik I didn't understand. When I dug deep it turned out to be macros! Just use real code and you'll be fine. No magic needed.
son of parnas
Monday, April 09, 2007
 
 
Macros have their places. I've used them excessively in a cryptographic library for optimization purposes, where loop unrolling of complex operations have a significant impact on performance. But that was plain C, not C++, I've used them on a local scope and I was painfully careful about #undef'ing them when they were no longer needed, going so far to include a macros_define.h and macros_undef.h for some commonly shared ones.

Using them for larger code structures on a global scope just sounds wrong. If you really want to use them, then make their lifetime as local as possible and comment, comment, comment. Global and uncommented macros have an inevitable tendency to jump in your way.
Secure
Monday, April 09, 2007
 
 
The two things that bother me the most about this approach is that it:

1) effectively hides the class structure from follow on developers, and
2) essentially locks the class structure to today's understanding of the problem.

By using a less common method to define classes, one puts greater knowledge requirements on future maintainers.  Plus, it requires greater knowledge to reverse engineer a macro than to write it initially.

Second, by binding this set of classes together now, especially with a non-standard mechanism, one limits class evolution.  It is often common to discover later that one of the classes is not really like the others and needs to be separate. 

In general, to write maintainable code, it is best not to push the language to its limits.  Stay within the confines of the most common constructs and you will save both others and yourself time in the long run.
Wayne M.
Monday, April 09, 2007
 
 
With some of the very specialized exceptions noted above, there should almost never be a need macros. If you can do what you want any other way, always choose the other way.

As for the title of this thread, if there is any sign of macros being resurrected,  your best tool is lots of garlic and  a big wooden stake!
DJ Clayworth
Monday, April 09, 2007
 
 
Macros are fine (as fine as Global Variables!).
Just do not abuse them.
Here is a nice one for users of old APIs.
You know, how annoying it gets, when you need to send messages and
every time to do so - there is a need to type-cast the data to WPARAM and LPARAM.
Here is one to the rescue:

#define ASK(h,m,wp,lp)    SendMessage (h, m, (WPARAM) (wp), (LPARAM) (lp))

Going further:

#define LBOX_COUNT(hList)    (int) ASK (hList, LB_GETCOUNT, 0, 0)

So, you can do this:

int nItems = LBOX_COUNT (hList);

Now, will someone tell me if the code is more readable or not?
asmguru62 Send private email
Tuesday, April 10, 2007
 
 
As I said, prefer other methods when they will work. I would code that as:

inline static
int Ask(HANDLE h, int m, int wp, int lp)
{
  return (int) SendMessage(h, m, (WPARAM) wp, (LPARAM) lp);
}

inline static
int LBox_Count(HANDLE h)
{
  return Ask(h, LB_GETCOUNT, 0, 0);
}

OK, you probably need to provide a couple other versions of Ask that take void* parameters, but how hard is that really? You could even use a template to allow any types for wp and lp.

The whole thing is so primitive anyway. Why not have an LBox class that wraps the handle and provides a Count method? We're talking about C++, after all.

int nItems = lbox.Count();
Mark Ransom Send private email
Tuesday, April 10, 2007
 
 
I have to say I really don't like this code sample at all.

What's wrong?

a) ASK means something very different from SendMessage, and therefore a confusing name.
b) ASK is a very short name. Short names are more likely to be defined more than once, which wouldn't work out too well.
c) The main purpose of this thing is to hide the unsafe, C style cast of a pointer to WPARAM. Personally I'd rather see it. As it stands, your macro will result in a macro function that casts practically anything to a WPARAM and LPARAM, sight unseen. This is also bad.

What's the right way to do this?

In C it would be preferable to define a regular function taking a couple of void pointers as parameters, and put the unsafe casts inside it. This has all the lack of type safety of your earlier example, but at least it's a function rather than a macro.

Or you could define a whole set of functions, effectively encapsulating the non-type-safe SendMessage within type-safe functions.

In C++, you don't have the implicit conversion of any pointer to a void* any more. Instead, it would be preferable to define a template function which specialises the parameters as required, and contains within it the casts of these parameters to the WPARAM and LPARAM, and has a typed interface on the outside.

Perhaps it would even be worthwhile specify in advance the allowed set of casts - creating a reasonably typesafe SendMessage. And it would be better to use static_cast rather than the C style cast.
Duncan Sharpe Send private email
Tuesday, April 10, 2007
 
 
OK, so I clearly don't use WPARAM and LPARAM often - they aren't necessarily pointers.

But the point remains. The best thing to do with an unsafe cast is encapsulate it. The next best thing is to publicly display it. The worst thing is to hide it.
Duncan Sharpe
Tuesday, April 10, 2007
 
 
So an example

#define LINK(property, val) \
template <> \
class Link<Key::Property> { \
public: enum { value = val }; }

LINK(One, 1);
LINK(Two, 2);
LINK(Three, 3);

#undef LINK

Can generate lots of types.  You can change all of the related classes in one type. 
It saves typing, so, no harm, no foul?

Tuesday, April 10, 2007
 
 
> #define ASK(h,m,wp,lp)    SendMessage (h, m, (WPARAM) (wp), (LPARAM) (lp))

That's not good. There's no type safety here at all. And why not have a method object object.ask() so you can just pass the message and not all the parameters?
son of parnas
Wednesday, April 11, 2007
 
 
I think I can make it worse (or better, depends on a reader).
Here is a use of macros you have never seen. You know how you have to
check every return code from every API or whatever? If you do not do this - code is weak.

#1: Usually, you see the pyramids of IF() statements, like these:

if (...)
{
    if (...)
    {
        if (...)
        {
            // and so on, until the head spins
            // ...
        }
    }
}

#2: Very often there is a return in the middle of a function, when
something fails. Such code gets unmaintainable and leaky really fast.

Macros will help me to resolve both issues:

#define BEGIN_BLOCK(always_true)        while (always_true) {
#define END_BLOCK            break; }
#define VERIFY_STMT(expr)        if (! (expr)) break;

Now we write a complex procedure with these:

HRESULT foo (IUnknown* pIObject)
{
    HRESULT hr = E_FAIL;

    // Resources to use

    ISomething* pISomething = NULL;
    PWCHAR pwstrText = NULL;
    TAllocator* pAllocator = NULL;

    BEGIN_BLOCK (pIObject)
        VERIFY_STMT (SUCCEEDED (pIObject->QueryInterface (IID_xxx, &pISomething)))
        VERIFY_STMT (pISomething != NULL)
        VERIFY_STMT (pISomething->HasItems () == TRUE)
        VERIFY_STMT (pwstrText = new WCHAR [pISomething->Count])
        VERIFY_STMT (pAllocator = new TAllocator (pwstrText))
        VERIFY_STMT ((pAllocator->Allocate (...))
        // ... and so on ...

        hr = S_OK; // Nothing failed
    END_BLOCK

    // Full cleanup

    if (pISomething) {
        pISomething->Release ();
    }

    if (pAllocator) {
        delete pAllocator;
    }

    if (pwstrText) {
        delete [] pwstrText;
    }

    return hr;
}
asmguru62 Send private email
Wednesday, April 11, 2007
 
 
Alternatively one could write....


if (Operation1() &&
  Operation2() &&
  Operation3() &&
  Operation4() &&
  Operation5())
{
  //succeeded
}

The && operator is lazy - if any parameter is false, it doesn't go on to evaluate the others. It will evaluate the arguments left to right - this is standards defined. It has precisely the same effect. No Macros. And it's cleaner if you're asking me.

However, this isn't the proper answer to your problem either. Make little resource manager classes that clean up a resource when they're deleted. This is known as the "Resource Acquisition Is Initialisation" idiom, or RAII for short.

The flip side of the idiom is that destruction of the object is resource cleanup. These will make sure that a resource you've allocated is deallocated when the class goes out of scope.

Then simply return from the function when an operation fails. Any resources you've picked up will be automatically cleaned up when the objects go out of scope.

This method also works in the presence of exceptions. Your code does not.

I have to say that using a while loop as a once-through control structure is definitely innovative.

The thing you don't seem to get is this. If I'm a maintenance programmer and I come across a bunch of macros like this, I'm not happy. I won't be able to see what this stuff does, or what it means, just by looking at it. The apparent control flow within your block doesn't follow normal rules, and that isn't obvious to me as a maintainer.

In order to understand what the REAL control flow is, I have to read the innards of your macros, and unpick your clever idea of using an infinite while loop as a once-through device you can break at any point. It only hides the complexity superficially as I still have to go and read the implementation of your macros in order to understand it. So for me this doesn't make life easier.

Especially as there are better and more standard ways of doing this.
Duncan Sharpe
Wednesday, April 11, 2007
 
 
> Very often there is a return in the middle of a function, when something fails. Such code gets unmaintainable and leaky really fast

Weird, with a combination of RAII and exceptions, I never seem to have that problem. On the other hand, a C programmer pretending to use C++ but in fact doing nothing more than using the // single-comment feature may think it's a neat idea.

Honestly, most of the problems people have with C++ can be summarised as "I desperately want to do old-fashioned C style procedural code, and changing all my 'struct' definitions to a 'class' isn't as wonderful as I was promised". It's like Haskell programmers using monads to build up procedural stateful code - it may be possible to stretch the language that way, but it's the wrong thing to do.


> I have to say that using a while loop as a once-through control structure is definitely innovative.

It's standard macro trickery, when you want a macro that must be followed by a semi-colon and will play nicely with if() and other control structures. Cute when justifiable, but it's rarely justifiable.

Another popular tick is this: do { ... } while(false)

Thursday, April 12, 2007
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz