C++ const关键字 - 大量使用?

Jes*_*ica 23 c++ syntax const

在以下C++函数中:

void MyFunction(int age, House &purchased_house)
{
    ...
}


void MyFunction(const int age, House &purchased_house)
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

哪个更好?

在两者中,'年龄'都是按价值传递的.我想知道'const'关键字是否有必要:对我来说似乎是多余的,但也很有用(作为额外的指示,变量不会改变).

有没有人对上述哪个(如果有的话)更好有任何意见?

Joh*_*itb 65

首先,它只是一个实现细节,如果你把const放在那里,不要把它放在声明集(标题)中.只将它放在实现文件中:

// header
void MyFunction(int age, House &purchased_house);

// .cpp file
void MyFunction(const int age, House &purchased_house);
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

参数在定义中是否为const纯粹是一个实现细节,不应该是接口的一部分.

我经常没见过这种事,我也不这样做.使用参数const会比帮助更容易让我感到困惑,因为我会立即模式匹配 - 将其失败为"const int&age":)这当然与在另一个级别使用const完全不同:

// const is a *good* thing here, and should not be removed,
// and it is part of the interface
void MyFunction(const string &name, House &purchased_house);
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,const将影响函数是否可以更改调用者的参数.应尽可能经常使用此意义的Const,因为它可以帮助确保程序的正确性并改进代码的自我记录.

  • @Brian,我没有这么说.问题是关于何时按值传递参数.在这种情况下,当你将const放在顶层并使参数本身为const时,它就是一个实现细节. (9认同)
  • @gab:但是你不能在值的参数的constness上重载它.因此,litb的优秀建议. (5认同)
  • @San Jacinto - 调用者已经知道它不会在函数返回时修改,因为它不是从函数返回的:它是通过值传递的,所以你传递一个副本并且什么都没有.至于被调用者是否修改了他的本地副本,这是一个实现细节(在CPP文件中不在标题中). (3认同)
  • San,你确定你没有参考这个吗?`int foo(int&)`和`int foo(const int&)`确实不同. (3认同)
  • 我理解技术差异.我所说的是,作为一个程序员使用其他人的代码,我希望保证我的foo将会同样回来,或者我想知道它可以在其他人的代码中被更改.另外,我不想看.然而,我想,有很多事情可以说是这样一个事实,即后来的另一个人可以在不让我知道的情况下改变它,现在我同样被搞砸了. (2认同)
  • "只是通知签名的方法/功能,意图将在.cpp中完成什么" - >不,这完全是*不是*界面是什么.接口关于*不知道实现的功能和外观.我建议*不*建议在cpp中写const而不是在标题中:我建议写const不通:"我没有经常看到这种事情,我也不这样做.参数为const会让我更加困惑,而不是" (2认同)
  • @litb:看看这篇文章:http://www.linuxjournal.com/article/7629 (2认同)

Van*_*uan 43

我建议阅读Herb Sutter.特殊的C++.有一章"Const-Correctness".

"事实上,对于编译器,无论是否在值参数前面包含此const,函数签名都是相同的."

这意味着这个签名

void print(int number);
Run Code Online (Sandbox Code Playgroud)

实际上与此相同:

void print(int const number);
Run Code Online (Sandbox Code Playgroud)

因此,对于编译器,如何声明函数没有区别.并且你不能通过将const关键字放在pass by value参数前面来重载它.

进一步阅读,Herb Sutter建议:

"避免在函数声明中使用const值传递参数.如果不修改参数const,则仍然将参数const置于同一函数的定义中."

他建议避免这种情况:

void print(int const number);
Run Code Online (Sandbox Code Playgroud)

因为那个const令人困惑,冗长和多余.

但在定义中,您应该这样做(如果您不更改参数):

void print(int const number)
{
   // I don't want to change the number accidentally here
   ...
}
Run Code Online (Sandbox Code Playgroud)

所以,你会确信即使在1000行函数的主体之后你总是没有触及数字.编译器将禁止您将该数字作为非const引用传递给另一个函数.

  • 是的,整本书都是以getw为基础的.但这本书解释得更好,IMO. (2认同)

Ore*_*n S 22

恕我直言,这是过度使用.当你说'const int age,...'时你实际上说的是"你甚至不能改变你职能中的本地副本".你所做的实际上是通过强迫他在他想要改变年龄/通过非const引用传递它时使用另一个本地副本来降低程序员代码的可读性.

任何程序员都应该熟悉传递引用和传递值之间的区别,就像任何程序员应该理解'const'一样.

  • 强制程序员使用另一个局部变量名而不是重用正式输入参数是一件好事.通过为其指定新值将参数重新用于新目的,通过赋予该名称多个目的,使代码可读性降低.有些语言不允许这样,例如C#. (51认同)
  • -1.使用const实际上有助于调试.通过强制程序员使用LOCAL变量,它可以帮助她(和她的读者)真正理解变化不会在方法/函数之外传播. (25认同)
  • 哇,这个答案的downvotes数量是惊人的.对于那些被downvoted的人:使用const值来获取参数是没有优势的,实际上,按值获取参数可以通过const引用来停止伪参数,如果参数是临时的,则可以通过const引用来复制它.Matthieu M和Partial的评论看起来很业余.我们这些经验丰富的人会注意到参数是通过复制还是通过引用,而我一个人并没有编写更糟糕的代码,因此新手可以阅读它. (8认同)
  • 这一切都归结为"读者在编程中的水平是什么以及他们习惯了什么?" 对于初学者来说,异常,引用语义甚至隐式转换可能看起来不清楚且不可读.当我编写代码时,我试着说清楚.但是,我不打算让每个初学者都明白.对我来说更重要的是避免模式匹配(在他们的答案中由litb和idimba提到)经验丰富的程序员可能会错误地做(很像我可能会这样做).我希望读者能够理解复印件. (6认同)
  • 说实话,我现在也不明白这个低点是怎么回事.他提出了一个很好的观点,就是说当地事情对调用者没有任何影响.他对新当地力量创造的观点可能有点偏离外观(顺便说一下,http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/*鼓励*你直接修改参数),但我不认为它值得-5.正如其他人已经指出的那样,无论是否为const,它都不会将更改传播给通过值传递的参数,因此上面的"-1"注释也不完全正确. (5认同)
  • @Partial,不,它不是任何方式的"合同".如果是,那么如何说"在我的函数中,有两个for循环,一个while循环.该函数在某个地方还有一个非const变量,我保证可以更改",这是怎么回事?(是的,我夸大了,但它恰恰是同一点:这对于所讨论的函数来说是完全私有的,并且不以任何方式涉及调用者). (3认同)
  • @rlbond:"我们这些有经验的人"你是谁判断的?一个人在网站上的声誉不等同于一个人的经验!你的论证是有缺陷的.另外,请查看此链接的内容:http://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect (3认同)
  • @litb:"Matthieu M和Partial的评论看起来很业余.我们这些经验丰富的人会注意到参数是通过复制还是通过引用,我不会写一个更糟糕的代码,只是新手可以阅读它." 你明白他写的是什么吗?通过说Matthieu M评论和我的评论似乎是业余的,然后说"我们这些经验丰富的人"和"编写更糟糕的代码只是让新手可以阅读它"意味着他将我们与经验丰富的人分开. (3认同)
  • @litb:鼓励计划用于速度优化,这不是讨论的主题.对于"传播"问题:通过创建局部变量,您意识到不会传播更改.无论如何,我承认在那里是一个挑剔. (2认同)
  • @ partial,问题中显示的原型都不会改变实际参数(传递给函数的参数).两个`age`参数都是按值传递的.即使您传递信用卡号码,只要您按价值传递,它也不会改变. (2认同)
  • 作为一项规则,我将const放在那里.该函数可能需要在内部对其进行修改,并且无需制作副本只是为了遵循计算机科学中"过度使用"的贬义词"const correct".如果函数没有修改它,那么使它成为const. (2认同)
  • 等一下,所以这会被推翻,因为那些阅读答案的人不理解语言,并且传递值和传递参考之间的差异?好悲伤......来自我的+1 (2认同)
  • -1重新分配局部变量会增加错误的可能性.看看为什么功能风格越来越受欢迎. (2认同)

Loc*_*ree 9

您不仅表明它不会改变,它将阻止您的代码认为它可以并且它将在编译时被捕获.


Dir*_*tel 8

请参阅Meyers和'Effective C++',并使用const自由,特别是使用pass-by-reference语义.

在这种原子变量的情况下,效率无关紧要,但代码清晰度仍然有益.

  • 我会不同意传递值原始类型的情况.如果我在代码中看到它,我会做一个双重的,因为被调用者不能改变值(从调用者的角度来看).我想知道是否有一些错字,如果编写代码的人不理解传递值. (4认同)
  • @Dolphin:那么你应该阅读litb's(一如既往)对这个主题的优秀处理,学习并停下来思考.如果代码感觉"自然"或"习惯"是正确性的标准,那么没有人会开始将STL用于所有这些有趣的迭代器,这些迭代器在第一眼看上去非常不自然. (2认同)

AnT*_*AnT 7

好吧,正如其他人已经说过的那样,从C++语言的角度来看,上面对const函数签名没有影响,即函数类型保持不变,无论是否const存在.它在抽象C++语言级别上唯一的影响是你不能在函数体内修改这个参数.

但是,在较低级别const,给定足够聪明的编译器,应用于参数值的修饰符可以具有一些优化益处.考虑具有相同参数集的两个函数(为简单起见)

int foo(const int a, const double b, const long c) {
   /* whatever */
}

void bar(const int a, const double b, const long c) {
   /* whatever */
}
Run Code Online (Sandbox Code Playgroud)

让我们说代码中的某处,它们被称为如下

foo(x, d, m);
bar(x, d, m);
Run Code Online (Sandbox Code Playgroud)

通常,编译器在调用函数之前使用参数准备堆栈帧.在这种情况下,堆栈通常会准备两次:每次调用一次.但是聪明的编译器可能会意识到,由于这些函数不会更改其本地参数值(声明为const),因此为第一次调用准备的参数集可以安全地重用于第二次调用.因此,它只能准备一次堆栈.

这是一种相当罕见且模糊的优化,只有在调用时已知函数定义(相同的转换单元或高级全局优化编译器)时才能工作,但有时可能值得一提.

说它"毫无价值"或"没有效果"是不正确的,尽管使用典型的编译器可能就是这种情况.

值得一提的另一个考虑因素是性质不同.那里有编码标准,要求编码人员不要改变初始参数值,如"不要将参数用作普通局部变量,参数值应在整个函数中保持不变".这有点意义,因为有时它可以更容易地确定函数最初给出的参数值(在调试器中,在函数体内).为了帮助实施此编码标准,人们可能会const在参数值上使用说明符.值得与否是一个不同的问题......

  • 我认为sbi考虑到了别名.对于所有编译器都知道,foo的实现可以访问指向x的指针(可能它存储在全局中,或通过其他参数访问).foo可能会写这个,带有修改x的副作用,即使(foo的x副本)是const.那么必须传递给bar的值是x的新值,而不是传递给foo的旧值.除非编译器可以对此进行排除,否则它不能将x的潜在"陈旧"副本作为参数传递给bar.所以它比你想象的要难,因为混叠通常很难排除. (4认同)
  • 不,你错过了这一点.一旦将参数声明为'const'(如我的例子中所示),编译器就不必"找出"任何东西.它可以安全地假设在'foo'中没有任何东西可以合法地改变参数的值,这就是在那里使用'const'的重点.如果你以某种方式设法"破解"'const'给编译器的承诺,那么这是你自己的错,编译器不负责捕获它.行为未定义,所有赌注均已关闭.这正是'const'的工作原理,并始终在C/C++中工作. (3认同)