【问题标题】:Pass a parameter by const reference to a non-void function : is it considered as a side effect?通过 const 引用将参数传递给非 void 函数:它是否被视为副作用?
【发布时间】:2016-02-08 19:40:33
【问题描述】:

编写函数(不是过程)最简洁的方法是什么?

第二个解决方案是否被告知有“副作用”?

struct myArea
{
  int t[10][10]; // it could by 100x100...
};

方案一:传值

 double mySum1(myArea a)
 {
   // compute and return the sum of elements
 }

解决方案 2:通过 const 引用传递

double mySum2(const myArea & a)
{
  // compute and return the sum of elements
}

我更喜欢第一个(清洁功能),尽管它不太有效。但是当需要复制大量数据时,可能会很耗时。

感谢您的反馈。

【问题讨论】:

  • 这里最好的是更详细的一个(就像大多数 C++ 主题一样:/)。
  • 定义最干净的。但是在你不需要的时候复制是没有意义的。

标签: c++ function parameter-passing


【解决方案1】:

我对你的术语有很多疑问:

  1. 在 C 或 C++ 中没有“过程”之类的东西。充其量,有些函数不返回值:"void"

  2. 您的示例没有"side effect"。

  3. 我不确定您所说的“干净的功能”是什么意思……但我希望您的意思不是“更少的源代码 == 更干净的代码”。事实并非如此:(

回答您的原始问题:

  1. 在您的示例中,double mySum1(myArea a) 会产生 完全不必要的复制的空间和 CPU 开销。不要这样做:)

  2. 在我看来,double mySum1(myArea & a) 或 double mySum1(myArea * a) 是等价的。就个人而言,我更喜欢double mySum1(myArea * a) ...但大多数 C++ 开发人员(正确!)更喜欢double mySum1(myArea & a)。

  3. double mySum1 (const myArea & a) 最棒的是:它具有 2) 的运行时效率,并且它表明你的意图是它不会修改数组。

    李>

PS: 我从以下测试生成了汇编输出:

struct myArea {
  int t[10][10];
};

double mySum1(myArea a) {
  double sum = 0.0;
  for (int i=0; i < 10; i++)
    for (int j=0; j<10; j++)
      sum += a.t[i][j];
  return sum;
}

double mySum2(myArea & a) {
  double sum = 0.0;
  for (int i=0; i < 10; i++)
    for (int j=0; j<10; j++)
      sum += a.t[i][j];
  return sum;
}

double mySum3(myArea * a) {
  double sum = 0.0;
  for (int i=0; i < 10; i++)
    for (int j=0; j<10; j++)
      sum += a->t[i][j];
  return sum;
}

double mySum4(const myArea & a) {
  double sum = 0.0;
  for (int i=0; i < 10; i++)
    for (int j=0; j<10; j++)
      sum += a.t[i][j];
  return sum;
}

如您所料,mySum1 有额外的代码来执行额外的复制。

然而,mySum2、mySum3 和 mySun4 的输出是相同的:

_Z6mySum2R6myArea:
.LFB1:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    pushq   %rbx
    movq    %rdi, -32(%rbp)
    movl    $0, %eax
    movq    %rax, -24(%rbp)
    movl    $0, -16(%rbp)
    jmp .L8
    .cfi_offset 3, -24
.L11:
    movl    $0, -12(%rbp)
    jmp .L9
.L10:
    movl    -16(%rbp), %eax
    movl    -12(%rbp), %edx
    movq    -32(%rbp), %rcx
    movslq  %edx, %rbx
    movslq  %eax, %rdx
    movq    %rdx, %rax
    salq    $2, %rax
    addq    %rdx, %rax
    addq    %rax, %rax
    addq    %rbx, %rax
    movl    (%rcx,%rax,4), %eax
    cvtsi2sd    %eax, %xmm0
    movsd   -24(%rbp), %xmm1
    addsd   %xmm1, %xmm0
    movsd   %xmm0, -24(%rbp)
    addl    $1, -12(%rbp)
.L9:
    cmpl    $9, -12(%rbp)
    setle   %al
    testb   %al, %al
    jne .L10
    addl    $1, -16(%rbp)
.L8:
    cmpl    $9, -16(%rbp)
    setle   %al
    testb   %al, %al
    jne .L11
    movq    -24(%rbp), %rax
    movq    %rax, -40(%rbp)
    movsd   -40(%rbp), %xmm0
    popq    %rbx
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
  <= mySum3 and mySum4 had different labels ... but identical instructions!

还值得注意的是,“const”的好处之一是它可以帮助编译器尽可能地执行几种不同类型的优化。例如:

【讨论】:

  • 我的意思是最干净的就是最接近数学的,所以可读性更强...(不是源代码长度)...但我明白在专业编程中,效率是重中之重...
  • @PatrickDezecache - 效率不一定是重中之重。问题在于,想要编写“最接近数学”的代码就像试图编写最接近俄语的英语一样。说母语的人充其量会认为你的口音很奇怪,最坏的情况是听不懂你。
  • @PeteBecker :我觉得声明一个函数“f(r) = 3.14r2”非常接近于“double f(r) {return 3.14*r*r;]”,不是它 ? ...有时我同意这不太明显...
【解决方案2】:

请注意,C++ 中没有“过程”之类的东西。不返回任何内容的函数仍然是函数。

现在问题是:如果您的参数是输出参数或输入/输出参数,也就是说,您希望调用者看到函数内部对传递给它的对象所做的更改,然后通过引用传递。否则,如果类型很小/复制起来非常便宜,则按值传递。否则,通过引用传递给 const。在你的情况下,我会通过引用 const。

【讨论】:

  • 是的,但是,我们使用非空函数的方式与空函数无关......
  • @PatrickDezecache 除了不返回值之外,void 函数没有什么特别之处。关于参数的规则没有区别。
  • @hvd ok 但是函数应该不能在框外查看(只有本地数据和本地参数值)。
【解决方案3】:

参数传递本身并不是副作用。

如果一个函数做了任何可观察的事情,而不仅仅是返回一个值,那将是一个副作用。
(例如修改引用参数、打印某些内容、修改任何全局状态...)

也就是说,即使你通过非常量引用传递,副作用的存在取决于你是否修改了被引用的对象。

【讨论】:

  • 是的,但是,使用非常量引用,您允许函数潜在地修改源参数。
  • @PatrickDezecache 仅仅因为一个函数可能有副作用,并不意味着它总是有。只有当它对某些东西产生影响而不是它主要打算影响的东西时,它才会产生副作用,无论它是否有可能影响其他任何东西。
  • @PatrickDezecache 即使您按值传递所有参数,函数也可以修改全局变量或写入文件,这是副作用。你无法从函数的参数中判断它是否有任何副作用。
  • @molbdnilo :我认为一个干净的函数(非 void)只显示访问他自己的数据、参数(const),仅此而已。没有全局数据,没有读写任何东西......就像数学方式......
猜你喜欢
  • 2012-02-25
  • 1970-01-01
  • 1970-01-01
  • 2013-11-08
  • 2014-05-29
  • 2021-11-19
  • 1970-01-01
  • 1970-01-01
  • 2017-05-19
相关资源
最近更新 更多