【问题标题】:C++ preprocessor concatenationC++ 预处理器连接
【发布时间】:2011-04-22 12:52:40
【问题描述】:

我有一个带有函数指针的函数构建。我认为尝试将此功能与预处理器宏交换可能会更快。至少,我想试试这个宏,这样我就可以测量它是否会生成更快的代码。

差不多是这样的:

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

我认为可以将其替换为以下内容:

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

但它给了我这样的错误:

错误:粘贴“)”和“setA”未提供有效的预处理令牌

有没有办法将函数名和它的对象连接起来?

【问题讨论】:

  • @Donotalo 代替 doStuff(&item, &Item::a, &Item::setA, _a);我称之为 DO_STUFF(ptr_to_item, a, setA, _a);
  • 如果您只是想测试性能并且无法让繁琐的宏工作,请不要使用宏。只需将宏替换所产生的代码手写写出来即可。

标签: c++ concatenation c-preprocessor


【解决方案1】:

使用内联函数比使用宏更好。使用任何好的编译器,这将给您提供与宏相同的效率,但如果您需要,还可以添加类型检查和调试符号。

【讨论】:

  • +1,我同意。使用这样的宏很讨厌,而且可能不需要。
  • 我只想测量调用成员函数指针的成本,而不是仅仅调用函数。
  • 如果函数值得内联,那么在生成的程序集中就不会有函数调用。如果它不值得内联,那么编译器可能知道它在做什么,并且宏 hack 不会为其添加任何显着的加速。
  • 运行时代码和编译时宏的效率一样吗?我想我必须看到一些证据。听起来不正确。
  • @Jay:我认为 JackN 是泛泛而谈。 如果函数是内联的,那么为什么它的性能不应该与宏几乎相同呢?事实上,目标代码应该几乎相同。通过指向成员函数的指针调用函数a 和setA 是否实际上会被内联是另一回事,所以这可能是一个相当不寻常的情况。宏与函数的不同之处在于它消除了优化的潜在障碍。
【解决方案2】:

令牌粘贴的意思是“将两个令牌组合成一个令牌”。

你不想这样。 ptr_to_item->a() 不是一个令牌。假设ptr_to_item是变量名,则为5:ptr_to_item、->、a、(、)。

你的宏应该是:

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

顺便说一句,对于讨厌宏的人来说,这避免了宏,同时也避免了使用指向成员函数的指针作为函数参数。如果由于通过未内联指针的调用而使宏比提问者的函数更快,则可以尝试。我不知道它是否/何时会有所作为:

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

因为它就在那里。

至少有一个弱点。 U 不能是模板中的引用类型。但由于它在问题的代码中有效地固定为int,因此模板参数 U 可能根本不需要,所以希望这不会太限制。

【讨论】:

    【解决方案3】:

    成员指针通常不利于效率。它们也不利于安全,因为 C++ 类型系统在可访问性方面存在漏洞。

    而不是您当前为doStuff 设计的...

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

    ...考虑...

    int foo( int value )
    {
        // .. Do some stuff 
    
         return newValue;
    }
    
    //...
    item.setA( foo( item.a() ) );
    item.setB( foo( item.b() ) );
    item.setC( foo( item.c() ) );
    

    摆脱Item 类的setter/getter 设计可能会更加简化事情。无论如何,我建议您尝试重新设计。为此,请记住 Item 实例的职责,以及它需要哪些知识。

    干杯,

    --阿尔夫

    【讨论】:

    猜你喜欢
    • 2012-03-28
    • 2015-09-13
    • 2014-07-30
    • 1970-01-01
    • 1970-01-01
    • 2016-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多