将函数实现为自由函数而不是C ++中的成员是否有任何优势?

时间:2016-04-23 10:15:57

标签: c++ class c++11 optimization

我对技术物流感兴趣。是否有任何优势,例如保存的内存等,来实现处理类的某些函数?

特别是,将运营商超载作为免费功能实现(假设您不需要访问任何私人会员,即使这样,您也可以让他们使用非会员的朋友)?

每次创建对象时,是否为类的每个函数提供了不同的内存地址?

6 个答案:

答案 0 :(得分:5)

关于使用非成员函数实现功能优于函数成员的情况,有各种各样的文章。

示例包括

Scott Meyers(#34; Effective C ++&#34;&#34; Effective STL&#34;等等)的作者关于非成员如何改进封装的作者:http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197 < / p>

Herb Sutter在他的周大师系列#84&#34; Monoliths Unstrung&#34;。基本上他提倡,当可以作为成员或非成员非朋友实现功能时,更喜欢非成员选项。 http://www.gotw.ca/gotw/084.htm

答案 1 :(得分:3)

这个答案可以帮助您:Operator overloading : member function vs. non-member function?。一般来说,如果您需要在您无法访问代码源的类上实现运算符(考虑stream s)或左操作数不是类,那么自由函数是必需的输入(例如int)。如果你控制类的代码,那么你可以自由地使用函数成员。

对于您的上一个问题,不,函数成员是唯一定义的,并且使用对象内部表来指向它们。函数成员可以被视为具有隐藏参数的自由函数,该参数是指向对象的指针, o.f(a)f(&o,a)或多或少相同,原型大致相似f(C *this,A a);

答案 2 :(得分:2)

static成员函数具有隐式this参数。 如果您的函数不使用任何非static成员,则它应该是自由函数或static成员函数,具体取决于您希望它在哪个命名空间。这样可以避免人类读者的混淆(他们会摸不着头脑,寻找不是static)的原因,并且代码大小会有一点改进,性能可能无法衡量。

要明确:asm中的static成员函数和非成员函数之间没有区别。 static-member和global或static(文件范围)之间的选择纯粹是命名空间/设计质量问题,而不是性能问题。 (在Unix共享库(位置无关代码)中,调用全局函数具有通过PLT的间接级别,因此更喜欢static文件范围函数。这是static关键字vs的不同含义.global static - 成员函数,它们是全局可见的,因此受symbol interposition的约束。)

这个规则的一个可能的例外是将大多数args传递给另一个函数的包装函数的好处是使它们的args与它们调用的函数的顺序相同,因此它们不必在寄存器之间移动它们。例如如果一个成员函数对类成员做了一些简单的事情,然后使用相同的arg列表调用static成员函数,它实际上没有隐式this指针,所以所有的args必须移动一个注册

大多数ABI使用args-in-register调用约定。 32位x86(除了一些Windows调用约定)是我所知道的主要异常,其中所有args总是在堆栈上传递。 64位x86传递寄存器中的前6个整数args,以及xmm寄存器中的前8个FP args(SysV)。或者寄存器中任何一种类型的args的前4个args(Windows)。

传递对象指针通常会在每个调用站点执行额外的指令或两次。如果隐式的第一个arg在有限的arg-pass regs集合中碰到任何其他args,那么它必须在堆栈上传递。这为涉及存储加载往返的arg以及被调用者和调用者中的额外指令的关键路径增加了几个延迟周期。 (有关该平台的更多详细信息,请参阅 wiki。

内联当然会消除这种情况。 static函数也可以通过现代编译器进行优化,因为编译器知道所有调用都来自它可以看到的代码,因此它可以使它们成为非标准函数。 IDK,如果任何编译器将在进程间优化期间丢弃未使用的args。链接时和/或整个程序优化也可以减少或消除未使用的args的开销。

代码大小始终至关重要,因为较小的二进制文件从磁盘加载速度较快,而在I-cache中错过较少。除非你专门设计一个对它敏感的实验,否则我不指望任何可测量的速度差异。

答案 3 :(得分:1)

一个严格的技术差异,也适用于static与非static成员函数,可能会影响极端情况下的效果:

对于成员函数,this指针将作为&#34;不可见&#34;函数的参数。通常,根据参数类型,固定数量的参数值可以通过寄存器而不是通过堆栈传递(寄存器读写速度更快)。

如果函数已经明确地使用了的参数数量,那么将其作为非static成员函数可能会导致参数通过堆栈传递而不是通过寄存器,而如果发生,除非可能发生或不发生的优化,函数调用更慢。

然而,即使速度较慢 - 在这种情况下,在你可以想象的绝大多数用例中,较慢是微不足道的(但是真实的)。

答案 4 :(得分:0)

根据主题,课程功能可能不是正确的解决方案。类函数依赖于在非正确函数的参数之间存在不对称的假设,其中它是函数的主要主体(id est隐式传递了它实际上对应于通过引用传递对象)。另一方面,很多时候这样的不对称可能不存在。在这些情况下,免费功能是最佳解决方案。 关于执行速度没有任何区别,因为类的方法只是第一个参数是this指针的函数。所以它完全等同于corrispetive非类函数,其中第一个元素是指向对象的指针。

答案 5 :(得分:0)

设计课程时要考虑的最重要的事情是“什么是不变量?”类是为保护不变量而设计的。因此,类必须尽可能小,以确保不变量得到适当保护。如果您有这么多会员/朋友功能,则需要查看更多代码。

从这个角度来看,如果一个类具有不需要保护的成员(例如,一个布尔值,其对应的get / set函数可以由用户自由更改),那么最好放置该属性as public并删除get / set函数(或多或少,这些是Bjarne Stroustrup的单词)。

那么,必须在类中声明哪些函数以及哪些函数?内部函数必须是这些必需的最小函数集来保护不变量,而外部函数必须是可以使用其他函数实现的任何函数。

运算符重载的东西是另一个历史,因为将一些运算符放在其中以及其他一些运算符的标准是由于与隐式转换相关的语法问题等等:

class A
{
private:
   int i_i;

public:
   A(int i) noexcept : i_i(i) {}
   int val() const noexcept { return i_i; }

   A operator+(A const& other) const noexcept
   { return A(i_i + other.i_i); }
};

A a(5);
cout << (4 + a).val() << endl;

在这种情况下,由于运算符是在类中定义的,编译器找不到运算符,因为第一个参数是一个整数(当调用一个运算符时,编译器会搜索在其中声明的自由函数和函数)第一个论点的类。)

在外面宣布:

class A
{
private:
   int i_i;

public:
   A(int i) noexcept : i_i(i) {}
   int val() const noexcept { return i_i; }
};

inline A operator+(A const& first, A const& other) const noexcept;
{ return A(first.val() + other.val()); }

A a(5);
cout << (4 + a).i_i << endl;

在这些情况下,编译器找到运算符,并尝试使用正确的A的构造函数执行第一个参数从int到A的隐式转换。

在这些情况下,操作符也可以使用其他函数实现,因此,它不需要是friend,并且您可以确定不变量不会受到该附加函数的影响。因此,在这些具体的例子中,将操作员移到外面有两个原因。