通过 const 引用将参数传递给非 void 函数:它是否被视为副作用

Pass a parameter by const reference to a non-void function : is it considered as a side effect?

本文关键字:是否 副作用 函数 void 引用 const 参数传递 通过      更新时间:2023-10-16

编写函数(不是过程(的最干净方法是什么?

第二种解决方案是否被告知有"副作用"?

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

解决方案1:按值传递

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

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

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

我更喜欢的是第一个(清洁功能(,尽管效果较差。但是,当有大量数据要复制时,可能会非常耗时。

感谢您的反馈。

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

  1. 在 C 或 C++ 中没有所谓的"过程"。 充其量,有些函数不返回任何值:"void">

  2. 您的示例没有"副作用"。

  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(,并且它表明您的意图不会修改数组。

附注:我从以下测试生成了程序集输出:

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"的好处之一是它可以帮助编译器尽可能执行几种不同类型的优化。 例如:

  • const 在 C/C++ 中提供了什么样的优化?(如有(

  • C++"康斯特"宣言:为什么和如何

请注意,

C++中没有"程序"这样的东西。不返回任何内容的函数仍然是函数。

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

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

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

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