C 操作员过载样式,这更适合优化
C++ Operator overloading styles, which is better for optimisation?
有两种方法可以在C 中超载二进制运算符:作为成员函数或作为非成员函数。
由于成员函数样式可以包含在.h文件中,并且非成员样式将在.cpp文件中,因此我认为成员函数样式将使编译器更容易优化。
- 成员和非成员运营商之间的性能有差异吗?
- 会员和非成员运营商是否会产生相同的代码(在任何编译器中)?
声明为成员是否与优化无关。编译器通常足够聪明,可以在这两种情况下都会产生同样优化的代码。而且,无论如何,对于精确编译器,版本和配置而言,这是有意义的,并且可以更改另一个。
对于可读性和可固定性问题,操作员应为成员...除非不能。如果可以的话,它允许在与OOP相干的类定义中声明它:它在该类的对象上运行,让我们尝试使其成为该类的方法。
只是在某些情况下根本不可能。例如,输出流注射器(<<
运算符)可以接受任何类的第二操作数,因此不能将其定义为成员。这就是C 允许运算符的非成员定义的原因。
如果您打算声明超载二进制运算符的成员和非成员版本,那么它们应该大多是等效的:
struct A {
A operator+(const A& other) const; // 1
};
A operator+(const A& x, const A& y) const; // 2
一个很小的差异可能是某些编译器可能会对成员版本使用更有效的此通话式约定(前提是它们通过this
指针通过寄存器而不是将其推在堆栈上,并且他们不利用该寄存器默认情况下,快速计数呼叫约定)。
二进制操作员的另一个可能的签名是按值进行参数:
A operator+(A x, A y) const;
但是,此声明仅适用于非会员版本。会员版本的最接近类似物是:
struct A {
A operator+(A other) const;
};
等效于
A operator+(const A& x, A y) const;
现在,考虑到通过值或(const)引用之间的传递不是您可以忽略的。如果您认为通过参考可以更有效地进行参考,那么您会误会。带有微不足道的构造函数/破坏者的小型类更好地按价值传递。在某些情况下,即使逐个值逐一通过较大的类也可能导致更好的优化,因为它允许编译器忘记 Aliasing 。
在进行一些研究之后,我可以说答案是肯定的,选择成员函数(在.h文件中使用代码)或非成员函数(在.cpp文件中使用代码)对编译器优化代码的能力有一定影响。
这是一个简单的示例。我在这里有两个操作员,一个是成员函数,并在.h文件中定义。另一个是一个非成员函数,并在.cpp文件中定义。
首先,班级的.h文件:
// test_class.h
// ------------
class Vector3D
{
public:
Vector3D(double xx, double yy, double zz)
: x(xx),
y(yy),
z(z)
{}
Vector3D operator +(Vector3D v)
{
return Vector3D(x+v.x, y+v.y, z+v.z);
}
friend Vector3D operator -(Vector3D v1, Vector3D v2);
double x,y,z;
};
现在班级的.cpp文件
// test_class.cpp
#include "test_class.h"
Vector3D operator -(Vector3D v1, Vector3D v2)
{
return Vector3D(v1.x-v2.x, v1.y-v2.y, v1.y-v2.y);
}
最后,主CPP文件:
// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
#include "test_class.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
Vector3D a(1,2,3);
Vector3D b(4,5,6);
Vector3D c(0,0,0);
Vector3D d(0,0,0);
c = a+b;
cout << c.x << c.y << c.z << endl;
d = a-b;
cout << d.x << d.y << d.z << endl;
return c.x + d.x;
}
查看汇编器输出,我可以看到成员函数产生的说明少于成员函数:
; 20 : c = a+b;
; 21 : cout << c.x << c.y << c.z << endl;
push DWORD PTR __imp_?endl@std@@YAAAV?$basic_ostream@....
movsd xmm0, QWORD PTR $T2[ebp+16]
movsd xmm1, QWORD PTR __real@4014000000000000
mov ecx, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@....
sub esp, 24 ; 00000018H
movsd QWORD PTR [esp+16], xmm0
movsd xmm0, QWORD PTR __real@401c000000000000
movsd QWORD PTR [esp+8], xmm0
movsd QWORD PTR [esp], xmm1
call DWORD PTR __imp_??6?$basic_ostream@....
mov ecx, eax
call DWORD PTR __imp_??6?$basic_ostream@....
mov ecx, eax
call DWORD PTR __imp_??6?$basic_ostream@....
mov ecx, eax
call DWORD PTR __imp_??6?$basic_ostream@....
; 22 :
; 23 : d = a-b;
movsd xmm0, QWORD PTR __real@4010000000000000
sub esp, 24 ; 00000018H
mov eax, esp
sub esp, 24 ; 00000018H
movq QWORD PTR [eax], xmm0
movsd xmm0, QWORD PTR __real@4014000000000000
movq QWORD PTR [eax+8], xmm0
movq xmm0, QWORD PTR _b$[ebp+16]
movq QWORD PTR [eax+16], xmm0
movsd xmm0, QWORD PTR __real@3ff0000000000000
mov eax, esp
movq QWORD PTR [eax], xmm0
movsd xmm0, QWORD PTR __real@4000000000000000
movq QWORD PTR [eax+8], xmm0
movq xmm0, QWORD PTR _a$[ebp+16]
movq QWORD PTR [eax+16], xmm0
lea eax, DWORD PTR $T1[ebp]
push eax
call ??G@YA?AVVector3D@@V0@0@Z ; operator-
movq xmm0, QWORD PTR [eax+16]
movq xmm2, QWORD PTR [eax]
movq xmm1, QWORD PTR [eax+8]
; 24 : cout << d.x << d.y << d.z << endl;
mov ecx, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@....
add esp, 52 ; 00000034H
movsd QWORD PTR _d$3$[ebp], xmm2
push DWORD PTR __imp_?endl@std@@YAAAV?$basic_ostream@....
sub esp, 24 ; 00000018H
movsd QWORD PTR [esp+16], xmm0
movsd QWORD PTR [esp+8], xmm1
movsd QWORD PTR [esp], xmm2
call DWORD PTR __imp_??6?$basic_ostream@....
mov ecx, eax
call DWORD PTR __imp_??6?$basic_ostream@....
mov ecx, eax
call DWORD PTR __imp_??6?$basic_ostream@....
mov ecx, eax
call DWORD PTR __imp_??6?$basic_ostream@....
成员功能及其代表产生16个说明,而非会员功能及其代表产生37个说明!
为什么?这是因为在编译时,编译器只能从类的标题文件中提供信息。它没有类的.CPP文件可用,并且必须将成员功能视为黑匣子。
在成员函数示例中,编译器嵌入了所有功能代码,并设置为优化它。在这种情况下,它可以做得很好,因为它可以在编译时执行算术。
在非成员函数示例中,编译器必须将参数引用到堆栈上,并实际执行函数调用。结果它得到的结果是一个完全惊喜。
但是,如果您的工具链支持整个程序优化,则很可能能够在链条稍后执行此优化。如果我打开这种类型的优化,则两个功能会产生相同数量的指令。
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 返回值优化:显式移动还是隐式
- 人脸跟踪arduino代码的优化
- <<操作员在下面的行中工作
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 纯函数,为什么没有优化
- 为什么大多数 pair 实现默认不使用压缩(空基优化)?
- 如何以优化的方式同时迭代两个间距不相等的数组
- 小字符串优化(调试与发布模式)
- C++ 与操作员不匹配<<
- 浮点定向舍入和优化
- 操作员C++的模棱两可的过载
- 与任何算术操作员都会影响优化的恒定操作数顺序
- 通过模板C++操作员优化
- C 操作员过载样式,这更适合优化
- 使用和操作员进行微优化
- RVO和NRVO优化+C++11移动操作员
- Clang链接时间优化与替换的操作员新的原因不匹配的自由()/删除在valgrind