【问题标题】:Why do we not pass POD by reference in functions?为什么我们不在函数中通过引用传递 POD?
【发布时间】:2011-09-26 21:42:24
【问题描述】:

一直有人告诉我,我们不应该通过引用传递 POD。但最近我发现引用实际上根本不占用内存。

那我们为什么选择写作:

void DoSomething(int iNumber);

代替:

void DoSomething(const int& riNumber);

不是更有效率吗?

【问题讨论】:

  • 检查这个question out
  • 谁告诉你引用不占用内存?这不是普遍正确的,事实上,非内联函数的引用参数确实需要额外的内存。
  • 内置类型和 POD 是有区别的。 POD 类可以占用多少内存没有限制(据我所知)。如果它很大,参考绝对是一个好主意。例如,Windows API 经常使用大型 POD 类型。
  • 由于引用通常是指针的语法糖,我很确定引用将在堆栈上占用 sizeof(void *) 字节。当然,这一切都取决于你的实现,但不是用魔法尘埃做的。
  • 因为在某些情况下const int& 的值是int 的两倍。 POD 通常可以按值传递,除非您实际上想要对先前实例化的变量进行非常量引用,以便函数可以更改它的值。

标签: c++


【解决方案1】:

实际上在这种情况下(使用 int)按值传递可能更有效,因为访问传递的值只需要 1 次内存读取而不是 2 次。

示例(使用 -O2 优化):

int gl = 0;

void f1(int i)
{
    gl = i + 1;
}

void f2(const int& r)
{
    gl = r + 1;
}

int main()
{
    f1(1);

    f2(1);
}

组合

    .file   "main.cpp"
    .text
    .p2align 2,,3
.globl __Z2f1i
    .def    __Z2f1i;    .scl    2;  .type   32; .endef
__Z2f1i:
LFB0:
    pushl   %ebp
LCFI0:
    movl    %esp, %ebp
LCFI1:
    movl    8(%ebp), %eax
    incl    %eax
    movl    %eax, _gl
    leave
    ret
LFE0:
    .p2align 2,,3
.globl __Z2f2RKi
    .def    __Z2f2RKi;  .scl    2;  .type   32; .endef
__Z2f2RKi:
LFB1:
    pushl   %ebp
LCFI2:
    movl    %esp, %ebp
LCFI3:
    movl    8(%ebp), %eax
    movl    (%eax), %eax
    incl    %eax
    movl    %eax, _gl
    leave
    ret
LFE1:
    .def    ___main;    .scl    2;  .type   32; .endef
    .p2align 2,,3
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB2:
    pushl   %ebp
LCFI4:
    movl    %esp, %ebp
LCFI5:
    andl    $-16, %esp
LCFI6:
    call    ___main
    movl    $2, _gl
    xorl    %eax, %eax
    leave
    ret
LFE2:
.globl _gl
    .bss
    .align 4
_gl:
    .space 4

【讨论】:

  • 您能再解释一下吗?它必须被取消引用吗?我从来不知道……我很困惑
  • int 的值驻留在内存中,这意味着读取了 1 个内存。将指针添加到混合意味着您必须读取指针指向的内容,然后从该内存地址读取(2 次内存读取)。
  • @Mike 你确定引用实际上是一个指针吗?过去我也这么认为,直到我问我的 C++ 老师,他告诉我一个引用需要 0 个字节,并且与通过输入不同的名称来访问同一个变量相当
  • @Mike。这假设您在堆栈上传递参数(对于 C++ 代码,如果参数可以在寄存器中传递,则不是这样)。它还假设代码没有内联(然后再次失败)。像这样的整个思维模型会导致对编译器正在做什么并且通常不成立的一大堆误解。
  • @xcrypt:如果引用在本地用于它所引用的值并且只是另一个变量的别名,那么您的教师声明成立。但是当你在不知道值来自哪里的函数之间传递东西时,你需要传递代表原始对象的东西(在内部这通常是一个指针)。
【解决方案2】:

不通过引用传递 POD 似乎是一个过于笼统的规则。 POD 可能很大,传递对它的引用是值得的。在您的特定情况下,int 与大多数(如果不是全部)实现在后台用于实际实现引用的指针大小相同。传递 int 或引用之间没有大小差异,但现在你有额外间接级别的惩罚。

【讨论】:

    【解决方案3】:

    引用传递就是变相的指针传递。

    对于小数据,将其作为值访问会更快,而不必多次引用指针。

    【讨论】:

    • 我认为没有现代架构可以比引用更快地传递值。
    • @long404:您能否支持您的说法,即传递一个 int 不会比获取指向此类 int 的指针(或需要时为临时指针)并传递此类指针更快?
    • @long404 当然不是。值将不得不将更多内容压入堆栈,但这可能比每次访问它时都推迟引用指针要快。引用传递更快(对于结构),值访问更快。对于单个整数,您应该尽可能按值传递。
    • @k-ballo:当然。正如 Pubby8 所说,它是一个伪装的指针,复制它就像复制一个机器字一样多。通常这是一条指令,并且寄存器通常用于此类参数。您是否知道某些东西被复制得更快的情况?
    • @Pubby8:完全同意(特别是如果您必须更改页面)。但该声明是为了“传递它”而不是访问它。
    【解决方案4】:

    因为它在 99.999% 的时间内是无意义的效率提升,而且它改变了语义。它还可以防止您传入一个常量值。例如:

    void Foo(int &i) { }
    Foo(1);  // ERROR!
    

    但是这会起作用:

    void Foo(const int &i) { }
    Foo(1);
    

    此外,这意味着该函数可以修改 i 的值,使其对调用者可见,这很可能是一件坏事(但同样,您当然可以使用 const 引用)。它归结为优化程序中重要的部分,并使其余代码的语义尽可能正确。

    引用实际上完全不占用内存。

    不确定是谁告诉你的,但这不是真的。

    【讨论】:

    • 同意。引用只是一个常量指针,因此它需要一个机器字的大小。
    • Re 引用实际上根本不占用内存。:当引用是本地自动变量时,情况可能是这样。那么引用只是一个别名。当引用是函数的参数或类中的数据成员时,最好将引用视为变相的指针。
    • @DavidHammen 局部自动变量?你能给我举个例子吗?
    • @xcrypt: void foo () { int bar = 42; int & bar_ref = bar; ... } 这里bar_ref 真的可以只是bar 的别名。编译器可以跳过为bar_ref 创建任何额外内存。还可以免费为bar_ref创建内存;编译器如何实现引用取决于编译器供应商。只要编译器确实表现出正确的行为,标准就不会在意。
    • @David:是的,但是当然,这个问题是在将引用作为函数参数的上下文中提出的。
    【解决方案5】:

    取决于你所说的效率。

    我们通过常量引用传递对象的主要原因是因为按值这样做会调用对象的复制构造函数,而这可能是一项昂贵的操作。

    复制int 很简单。

    【讨论】:

      【解决方案6】:

      没有正确答案,甚至没有明显的普遍偏好。在某些情况下,按值传递可以更快,而且非常重要的是,可以消除副作用。在其他情况下,通过引用传递可以更快,并允许更容易地从给定函数返回信息。这适用于 POD 数据类型。请记住,结构可以是 POD,因此 POD 之间的大小考虑也会有所不同。

      【讨论】:

        【解决方案7】:

        真正的答案是习惯
        我们有一种根深蒂固的文化,即尝试对我们的代码进行宏优化(即使它很少产生任何真正的影响)。如果您能告诉我代码通过值/引用(整数)传递有什么不同,我会感到惊讶。

        一旦您开始通过模板隐藏类型,我们就会再次开始使用 const 引用(因为复制某些类型可能很昂贵)。

        现在,如果您询问的是通用 POD,则可能存在成本差异,因为 POD 可能会变得非常大。

        第一个版本有一个小优势,如果我们改变原始值,我们不需要额外的标识符。

        void DoSomething(int iNumber)
        {
            for(;iNumber > 0; --iNumber)   // mutating iNumber
            {
               // Stuff
            }
        }
        
        // No real cost difference in code.
        // Just in the amount of text we need to read to understand the code
        //
        void DoSomething(int const& iNumber)
        {
            for(int loop = iNumber;loop > 0; --loop)   // mutating new identifier
            {
               // Stuff
            }
        }
        

        【讨论】:

          【解决方案8】:

          不,不是。与 int 的示例完全相同。 当你有“更重”的物体时,这很重要。

          【讨论】:

          • 根据这个推理,在 POD 上使用按值传递确实有意义。
          猜你喜欢
          • 2021-05-12
          • 2014-07-25
          • 2011-11-19
          • 1970-01-01
          • 2016-08-12
          • 1970-01-01
          • 1970-01-01
          • 2015-12-29
          相关资源
          最近更新 更多