C++ 与传递值和传递常数ref的程序集不同

C++ 与传递值和传递常数ref的程序集不同,c++,gcc,clang,C++,Gcc,Clang,假设我们有这样的代码: template<typename T> struct StrongValue{ constexpr const T &operator()() const { return value; } T &operator()(){ return value; } constexpr const T &get() const { return valu

假设我们有这样的代码:

template<typename T>
struct StrongValue{
    constexpr const T &operator()() const {
        return value;
    }

    T &operator()(){
        return value;
    }

    constexpr const T &get() const {
        return value;
    }

    T &get(){
        return value;
    }

    T value;
};

using myint = int; // try double too

using m = StrongValue<myint>;

myint sum2(const m &a, const m &b){
    return a() + b();
}

myint sum2a(const m a, const m b){
    return a() + b();
}

myint sum1(myint a, myint b){
    return a + b;
}

int main(){
    constexpr m a{5};
    constexpr m b{5};

    return sum2a(a, b);
}
sum2(StrongValue<int> const&, StrongValue<int> const&):
  mov eax, DWORD PTR [rsi]
  add eax, DWORD PTR [rdi]
  ret
sum2a(StrongValue<int>, StrongValue<int>):
  lea eax, [rdi+rsi]
  ret
sum1(int, int):
  lea eax, [rdi+rsi]
  ret
main:
  mov eax, 10
  ret
模板
结构strong值{
constexpr const T&运算符()()const{
返回值;
}
T运算符()(){
返回值;
}
constexpr const T&get()const{
返回值;
}
获取(&G)(){
返回值;
}
T值;
};
使用myint=int;//再试试双倍
使用m=strong值;
myint sum2(持续并购、持续并购){
返回a()+b();
}
myint sum2a(常数m a,常数m b){
返回a()+b();
}
myint sum1(myint a、myint b){
返回a+b;
}
int main(){
constexpr m{5};
constexpr mb{5};
返回sum2a(a,b);
}
在clang和gcc中,-O3汇编如下所示:

template<typename T>
struct StrongValue{
    constexpr const T &operator()() const {
        return value;
    }

    T &operator()(){
        return value;
    }

    constexpr const T &get() const {
        return value;
    }

    T &get(){
        return value;
    }

    T value;
};

using myint = int; // try double too

using m = StrongValue<myint>;

myint sum2(const m &a, const m &b){
    return a() + b();
}

myint sum2a(const m a, const m b){
    return a() + b();
}

myint sum1(myint a, myint b){
    return a + b;
}

int main(){
    constexpr m a{5};
    constexpr m b{5};

    return sum2a(a, b);
}
sum2(StrongValue<int> const&, StrongValue<int> const&):
  mov eax, DWORD PTR [rsi]
  add eax, DWORD PTR [rdi]
  ret
sum2a(StrongValue<int>, StrongValue<int>):
  lea eax, [rdi+rsi]
  ret
sum1(int, int):
  lea eax, [rdi+rsi]
  ret
main:
  mov eax, 10
  ret
sum2(StrongValue const&,StrongValue const&):
mov eax,DWORD PTR[rsi]
添加eax、DWORD PTR[rdi]
ret
sum2a(strong值,strong值):
lea eax,[rdi+rsi]
ret
sum1(整数,整数):
lea eax,[rdi+rsi]
ret
主要内容:
mov-eax,10
ret
为什么
sum2
是这样编译的

这是因为编译器将更改函数签名,如果它忽略引用,则不允许这样做


这是否意味着,如果不是内联的,
sum2
sum2a
更昂贵?

区别在于
sum2
的参数本质上是指针,而
sum2a
的参数是值。这意味着在
sum2
中,必须取消对指针的引用,以获得可以添加的实际值,而在
sum2a
中,可以立即添加值

过度使用常量引用是一个常见的错误。对于具有廉价复制的对象,通常最好按值传递参数

为什么sum2是这样编译的

这是意料之中的。对于sum2,您传递了两个引用。引用和 const 是C++的东西,CPU没有它们,对于CPU来说,它们只是指针。因此,在函数中,代码必须从内存中获取两个值并求和

另外两个版本按值接收参数

对于所有3个版本,编译器都选择使用
\uu regcall
调用约定,这就是为什么这两个参数在
RSI
RDI
寄存器中传递的原因。这就是其他两个版本如何在一条指令中计算结果

这是否意味着,如果不是内联的,sum2比sum2a更昂贵


一般来说,是的。您不应该通过常量引用传递整数,而是通过值传递它们。但是,确切的性能影响可以忽略不计,唯一的方法是分析。

但如果没有内联/静态/匿名命名空间,编译器不允许重写
sum2
,并且必须使用指针?如果是在外部文件中,函数将永远不会内联?编译器通常尊重函数原型。现代编译器有各种各样的功能,在这种情况下有帮助,VC++有全局优化,也称为链接时间代码生成,LLVM有链接时间优化。它们甚至可能跨文件内联这些简单函数,从而消除RAM引用。但为了获得最佳性能,更可靠的方法是以一种有助于编译器以更少的技巧生成高效代码的方式编写代码。