views:

148

answers:

3

I have a function build with function pointers. I think it might be faster to try to exchange this function with pre processor macro. At least, I would like to try out the macro so I can measure if it generates faster code.

It's more or less like this:

typedef int (Item::*GetterPtr)(void)const;
typedef void (Item::*SetterPtr)(int);

void doStuff(Item* item, GetterPtr getter, SetterPtr setter, int k)
{
    int value = (item->*getter)();

    // .. Do some stuff 

    (item->*setter)(newValue);
}

And it's called like

// ...
doStuff(&item, &Item::a, &Item::setA, _a);
doStuff(&item, &Item::b, &Item::setB, _b);
doStuff(&item, &Item::c, &Item::setC, _c);
// ...

I think it might be possible to swap this with something like:

#define DO_STUFF(item, getter, setter, k) do { \
int value = item ## -> ## getter ## (); \
//... \
item ## -> ## setter ## (newValue); \
} while(0);

but it gives me errors like:

error: pasting ")" and "setA" does not give a valid preprocessing token

There's a way to concatenate the function names and it's object?

+6  A: 

It is better to use inline functions than macros. Using any good compiler, this will give you the same efficiency as the macro, but with added type checking and debug symbols if you need them.

JackN
+1, I agree. Using macros like that is nasty, and probably not needed.
bde
I would just like to measure the cost of calling pointers to member functions instead of just calling the function.
Vitor Py
If the function is worth inlining, there will be nothing like a function call in the generated assembly. If it is not worth inlining, then the compiler probably knows what it is doing and a macro hack won't add any significant speedup to it.
dark_charlie
run time code is the same efficiency as compile time macros? I think I'd have to see some proof of that one. It doesn't sound correct.
Jay
@Jay: I think JackN is talking in generalities. *If the function is inlined*, then why shouldn't it have pretty much the same performance as the macro? In fact the object code should be pretty much the same. Whether the functions `a` and `setA` actually will be inlined when called through a pointer-to-member-function is another matter, so this might be a fairly unusual case. The macro is different from the function, in that it removes a potential barrier to optimisation.
Steve Jessop
-1 doesn't answer the question -- this should be a comment, not an answer
Chris Dodd
There are sometimes a macro is needed and an inline function won't work - for example if you use _alloca() to get a temporary buffer and it needs to persist after the macro is called. But for all operations that do work in an inline function, a good compiler should be able to generate identical or at least similarly performing code for an inline version compared to a macro version.
Adisak
@Steve: If task the macro expansion is doing is being replaced by executed code then it's never going to be faster. I think he probably should have said "macro generated code"
Jay
+3  A: 

Token-pasting means "combining two tokens to form a single token".

You don't want that. ptr_to_item->a() isn't one token. Assuming ptr_to_item is a variable name, it's 5: ptr_to_item, ->, a, (, ).

Your macro should just be:

#define DO_STUFF(item, getter, setter, k) do { \
    int value = (item)->getter(); \
    //... \
    (item)->setter(newValue); \
} while(0);

By the way, for the macro haters, this avoids macros while also avoiding the use of a pointer-to-member-function as a function parameter. It could be tried if the macro is faster than the questioner's function due to the call through a pointer not being inlined. I don't know if/when it will make a difference:

#include <iostream>

struct A {
    int f;
    int foo() {return f;}
    void setfoo(int a) { f = a; }
};

template <typename T, typename U, U (T::*GETTER)(), void (T::*SETTER)(U)>
void doit(T &obj, U k) {
    U value = (obj.*GETTER)();
    value += k;
    (obj.*SETTER)(value);
}

int main() {
    A a = {0};
    std::cout << a.foo() << "\n";
    doit<A,int,&A::foo, &A::setfoo>(a,1);
    std::cout << a.foo() << "\n";
    doit<A,int,&A::foo, &A::setfoo>(a,2);
    std::cout << a.foo() << "\n";
}

Because it's there.

There's at least one weakness. U can't be a reference type in the template. But since it's effectively fixed as int in the code in the question, the template parameter U may not be needed at all, so hopefully that isn't too limiting.

Steve Jessop
+4  A: 

Member pointers are generally ungood for efficiency. They're also ungood for safety, because there's a loophole in the C++ type system regarding accessibility.

Instead of your current design for doStuff like ...

typedef int (Item::*GetterPtr)(void)const;
typedef void (Item::*SetterPtr)(int);

void doStuff(Item* item, GetterPtr getter, SetterPtr setter, int k)
{
    int value = (item->*getter)();

    // .. Do some stuff 

    (item->*setter)(newValue);
}

//...
doStuff(&item, &Item::a, &Item::setA, _a);
doStuff(&item, &Item::b, &Item::setB, _b);
doStuff(&item, &Item::c, &Item::setC, _c);

... consider ...

int foo( int value )
{
    // .. Do some stuff 

     return newValue;
}

//...
item.setA( foo( item.a() ) );
item.setB( foo( item.b() ) );
item.setC( foo( item.c() ) );

Getting rid of the setter/getter-design for the Item class will probably simplify things even more. Anyway, I recommend that you try re-designing. For that, keep in mind the responsibilites of an Item instance, and what knowledge it needs for that.

Cheers & hth.,

– Alf

Alf P. Steinbach
Good to see you here Alf. `:-)`
Prasoon Saurav
@Alf you will enjoy this one: http://stackoverflow.com/questions/75538/hidden-features-of-c/1065606#1065606 :)
Johannes Schaub - litb