C++中的静态函数和成员函数,是否存在额外开销?

7

在C++类中,静态函数与成员函数相比是否存在额外开销。

class CExample
{
    public: 
        int foo( int a, int b) {
            return a + b ; 
        }
        static int bar( int a, int b) {
            return a + b ; 
        } 
};

我的问题是:

  • 在这个例子中,foo()或bar()哪个更有效率?
  • 为什么我不想把foo()变成静态函数,因为它没有改变任何成员变量?

2
第三个问题:为什么这两个函数都不是自由函数? - Xeo
5
你的编译器可能足够聪明,使这些代码和其他代码一样高效。 - James M
@JamesMcLaughlin:答案是它并不重要,但编译器无法使它们完全相同,其中一个具有一个隐藏的参数(this),而另一个则没有[整个论点是假设编译器没有将该函数内联]。 - David Rodríguez - dribeas
假设实现没有内联,我很想看到不使用平台等效的thiscall(即在从未使用的this指针上浪费寄存器)的编译器。有比声称没有区别更好的方法来证明差异永远不重要,当它快速时就会有一个。 - user395760
@rhalbersma:不完全正确,因为你需要特化整个类。函数模板的部分特化可以通过重载轻松实现。 - Xeo
显示剩余4条评论
4个回答

10
在这个例子中,foo()和bar()哪一个更有效率?不是的。两个调用都是静态解析的。将this指针传递给非静态函数可能会有一些开销,但在这种情况下,两者都很可能被内联。
我为什么不想将foo()变成静态函数,因为它没有改变任何成员变量?你不会的,实际上,将所有不绑定到实例的方法设置为static是一个好习惯。

+1:最后一句话有点过于笼统,但我同意你试图传达的情感。 - Martin York

6
这个答案专注于回答你问题的第二部分
引用自《C++编程规范:101条规则、指南和最佳实践》:

第44章。优先编写非成员非友元函数
[...] 非成员非友元函数通过最小化依赖关系来提高封装性 [...]

Scott Meyers提出了以下算法,用于确定哪些方法应该成为类的成员 (来源)

if (f needs to be virtual)
   make f a member function of C;
else if (f is operator>> or operator<<)
   {
   make f a non-member function;
   if (f needs access to non-public members of C)
      make f a friend of C;
   }
else if (f needs type conversions on its left-most argument)
   {
   make f a non-member function;
   if (f needs access to non-public members of C)
      make f a friend of C;
   }
else if (f can be implemented via C's public interface)
   make f a non-member function;
else
   make f a member function of C;

关于你问题的前半部分,我猜编译器会优化掉任何差异。

1

在某些编译器/情况下,bar可能会有更少的开销,因为它永远不需要放置在指针表中,也不需要额外的“this”参数。

如果foo不使用本地成员,则使其非静态的唯一原因是您打算对其进行重载,但由于它不是虚拟的,因此这并不起作用。


1
编译器很可能会优化掉对“this”的引用。很有可能这样简单的函数会被完全内联。 - Rafael Baptista
3
假设函数定义对编译器是可见的,这需要明确表明你的假设,否则一般情况下不能做出这样的假设。 - David Rodríguez - dribeas
许多现代编译器在链接时间后进行全局优化,以捕捉这种情况。 - Rafael Baptista
@RafaelBaptista 我上次的状态是,LTO要求至少显式传递一个晦涩的标志,存在一些问题,并且不像它本应该那样令人惊叹(即:只有像死代码删除这样的小优化,而不是像基于某些调用站点的常量参数专门化整个调用子图这样的大交易),因此很少使用。我错过了备忘录吗? - user395760
你可能是对的。在gcc中有-flto和-fwhole-program选项。我不认为它们默认开启。因此,这种优化可能不会默认发生。 - Rafael Baptista

0

可能没有区别,因为两个函数都没有访问数据成员,并且你没有从任何虚拟类派生。

但在一般情况下,“bar”会稍微快一点,因为作为成员函数的“foo”通常需要传递和使用一个额外的参数 - “this”。


1
你所说的infinitessimally是指不可测量的。即使存在实际差异(考虑到现代流水线处理器和现代调用约定可能不存在差异),由于有太多其他因素会淹没这种差异,因此这种差异永远不会被测量出来。 - Martin York
当没有访问类特定数据时,this指针将被优化掉。这与任何其他未被引用的函数参数相同。 - TemplateRex
同意。这显然是一个新手问题,我只是想说明一个原则,即所有条件相等的成员函数需要一个额外的参数,在原则上略微更昂贵。 - Rafael Baptista
@rhalberma:你的评论因为缺乏阅读理解和过于追求学究式的愚蠢而被扣了1分。我的回答第一行就是“没有区别”,正是出于这个原因。 - Rafael Baptista
1
@rhalberma:再扣你一个-1,因为你不理解编译器的工作原理。正如David Rodriguez在下面所说的那样,在函数定义对编译器不可见的情况下,this指针不会被优化掉。例如,如果这个函数是在动态库接口中定义的呢? - Rafael Baptista

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接