C++ 返回私有类成员是否比使用结构并直接访问该变量慢?

C++ 返回私有类成员是否比使用结构并直接访问该变量慢?,c++,performance,class,struct,C++,Performance,Class,Struct,假设您有一个类,该类具有私有成员,这些成员在程序中经常被访问(例如在必须快速的循环中)。想象一下,我定义了这样的东西: class Foo { public: Foo(unsigned set) : vari(set) {} const unsigned& read_vari() const { return vari; } private: unsigned vari; }; 我想这样做的原因是,一旦创建了类,“vari”就不应

假设您有一个类,该类具有私有成员,这些成员在程序中经常被访问(例如在必须快速的循环中)。想象一下,我定义了这样的东西:

class Foo
{
public: 

    Foo(unsigned set)
        : vari(set)
    {}

    const unsigned& read_vari() const { return vari; }

private:
    unsigned vari;
};
我想这样做的原因是,一旦创建了类,“vari”就不应该再被更改了。因此,为了最大限度地减少错误的发生,“当时这似乎是个好主意”

但是,如果我现在需要调用此函数数百万次,我想知道是否存在开销和减速,而不是简单地使用:

struct Foo
{
    unsigned vari;
};
那么,为了避免任何人在构造函数设置变量后错误地更改变量的值,我使用类的第一个可归罪权利是什么?
此外,这是否会以函数调用开销的形式引入“惩罚”。(假设我在编译器中使用优化标志,例如GCC中的-O2)?

它们应该是相同的。还记得你在向量上尝试使用
操作符[]
gdb
只是回答了
优化了
这一令人沮丧的时刻吗?这就是这里将要发生的事情。编译器不会在这里创建函数调用,而是直接访问变量

让我们看看下面的代码

struct foo{
   int x;
   int& get_x(){
     return x;
   }   
};

int direct(foo& f){ 
   return  f.x;
}

int fnc(foo& f){ 
   return  f.get_x();
}
它是用
g++test.cpp-o test.s-s-O2
编译的。
-S
标志告诉编译器“在正确编译阶段之后停止;不要汇编(引用g++手册页)。”这是编译器给我们的:

_Z6directR3foo:
.LFB1026:
  .cfi_startproc
  movl  (%rdi), %eax
  ret 

如您所见,在第二种情况下没有进行函数调用,它们都是相同的。这意味着在使用访问器方法时没有性能开销

奖励:如果关闭优化,会发生什么?相同的代码,下面是结果:

_Z6directR3foo:
.LFB1022:
  .cfi_startproc
  pushq %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register 6
  movq  %rdi, -8(%rbp)
  movq  -8(%rbp), %rax
  movl  (%rax), %eax
  popq  %rbp
  .cfi_def_cfa 7, 8
  ret

\u Z3fncR3foo:
.LFB1023:
.cfi_startproc
pushq%rbp
.cfi_def_cfa_偏移量16
.cfi_偏移量6,-16
movq%rsp,%rbp
.cfi_def_cfa_寄存器6
subq$16,%rsp
movq%rdi,-8(%rbp)
movq-8(%rbp),%rax
movq%rax,%rdi
调用_ZN3foo5get_xEv 35;可以获得相同的性能。很多C++类依赖于此——例如,C++ 11的列表::siz().const 可以预期地返回数据成员。(这与
vector()
形成对比,在这里,我将实现的
size()
计算为
begin()
end()对应的指针数据成员之间的差异。)
,如果优化人员无法确定
size()
在循环迭代中是恒定的,则以可能较慢的索引迭代为代价,确保典型迭代器的使用尽可能快


对于像
unsigned
这样的类型,通常没有特别的理由通过
const
引用返回,该类型无论如何都应该适合CPU寄存器,但是由于它是内联的,编译器不必照字面理解(对于离线版本,它可能通过返回必须取消引用的指针来实现)。(不典型的原因是允许获取变量的地址,这就是为什么say
vector::operator[](size_t)const
需要返回
const t&
而不是
t
,即使
t
小到可以放入寄存器。)

只有一种方法可以通过测量这两种变体,确定在使用特定工具和特定平台上的特定优化标志构建的特定程序中哪个更快


已经说过,二进制文件的指令是相同的,很有可能。正如其他人所说的,

,现在的优化器依赖于抽象(特别是C++中的)抽象,它们是非常非常好的。 但是你可能不需要这个getter

struct Foo {
    Foo(unsigned set) : vari(set) {}
    unsigned const vari;
};

const
并不禁止初始化。

它们不一样。使用该方法,您将返回一个副本,因此无法真正访问该变量。等效的只读版本是
const unsigned&read_vari()const{return vari;}
@juanchopanza。谢谢你的留言。我将很快更新我的问题。理论上,没有优化(特别是getter的内联),是的。实际上,在具有常规优化的发布版本中,两个选项都将编译为相同的汇编指令。所以是的,你的冲动是对的:)你可以自己检查装配并100%确定。与其希望有人知道你的编译器是做什么的:)实际上,如果你愿意的话,你可以在不进行优化的情况下发布代码,但在这一点上,getter的函数调用开销应该是你最后的担忧:所有模板化代码(STL/Boost)都会非常慢,以至于你甚至不会注意到其余的部分。@MatthieuM。你指的是什么?模板确实会使编译速度变慢,但会降低效率。你能提供一个源代码吗?事实上,这只是你答案的自然结果:大多数STL&Boost都是一层层的模板类和函数。当你在进行优化的情况下编译时,这些层被折叠在一起,直到几乎没有代码保留下来,所以速度很快;但是,如果没有优化,每一层都会增加自己的函数调用开销。只要看看平均
std::vector::push_back
实现,你就会明白我的意思:)@MatthieuM。好吧,我从来没有这样想过。
_Z3fncR3foo:
.LFB1023:
  .cfi_startproc
  pushq %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16 
  movq  %rsp, %rbp
  .cfi_def_cfa_register 6
  subq  $16, %rsp
  movq  %rdi, -8(%rbp)
  movq  -8(%rbp), %rax
  movq  %rax, %rdi
  call  _ZN3foo5get_xEv    #<<<call to foo.get_x()
  movl  (%rax), %eax
  leave
  .cfi_def_cfa 7, 8
  ret
struct Foo {
    Foo(unsigned set) : vari(set) {}
    unsigned const vari;
};