经过大量的Google搜索后,我发现很多关于将函数及其参数标记为const
的内容,但没有将变量标记为const
的指南。
这是一个非常简单的例子:
#include <string>
#include <iostream>
void example(const std::string& x) {
size_t length = x.length();
for (size_t i = 0; i < length; ++i) {
std::cout << x.at(i) << std::endl;
}
}
int main() {
example("hello");
}
为什么不做
size_t length = x.length();
const like
const size_t length = x.length();
按惯例?
我知道这么小的一个简单的例子确实没有给这个带来任何巨大的好处,但似乎它在一个更大的代码库中会有所帮助,你可能会意外地改变你不应该变异的变量。 / p>
尽管有这样的好处,但我并没有真正看到它使用那么多(在我见过的C ++代码库中)或提到的几乎与函数及其参数const
一样多。
除了必须输入5个额外字符外,还有一些缺点吗?我在这个话题上找不到太多东西,如果这是一个有这么多争议的问题,我不想在脚下射击。
答案 0 :(得分:40)
标记您不修改const
的变量没有任何缺点。
虽然存在一些问题:编译器将帮助您诊断何时无意中修改了您不应该/不应该修改的变量以及编译器可能(尽管由于语言原因) const_cast
和mutable
很少见,这会产生更好的代码。
所以,我建议;尽可能使用const
。没有缺点,您的编译器可以帮助您发现错误。没有理由不(除了一点额外的打字)。
请注意,这也扩展到了成员函数。尽可能让它们const
- 它允许它们在更多的上下文中使用并帮助用户推理代码(“调用此函数不会修改对象”是有价值的信息)。
答案 1 :(得分:19)
我至少可以想到两个缺点:
const
两者都值得。
冗长是一种经常听到的反对显性的论据,但是人们经常会误读速度与理解速度。在冗长和明确之间存在着平衡,当然,过于冗长可能会淹没有用的信息,但过于隐含/简洁可能无法呈现必须重建/推断/推断的信息/。
就个人而言,我使用强类型的静态检查语言,以便编译器尽早发现我的错误;使用const
进行注释既向读者提供信息又编译器。我认为值得额外的6个符号。
至于惯性,删除const
可能只是改变的一小部分......它通过强迫你经历所有使用它的地方来回报自己,并检查周围的代码以确保它实际上是好吧删除这个const
。突然修改以前不可变的代码路径中的特定数据片段需要确保代码路径(或其调用者)的任何部分都不会意外地依赖于这种不变性。
答案 2 :(得分:7)
而不是这个非标准代码:
#import <string>
#import <iostream>
void example(const std::string& x) {
size_t length = x.length();
for (size_t i = 0; i < length; ++i) {
std::cout << x.at(i) << std::endl;
}
}
int main() {
example("hello");
}
......我写这个:
#include <string>
#include <iostream>
using namespace std;
void example( string const& s )
{
for( char const ch : s )
{
cout << ch << '\n';
}
}
auto main()
-> int
{ example( "hello" ); }
相对于原始代码,我可以添加const
的主要位置是循环中的ch
变量。我觉得这很好。 const
通常是可取的,因为它减少了必须考虑的代码操作,而基于范围的循环可以让您拥有更多const
。
在大多数情况下使用const
的主要缺点是,当您必须与C API相关时。
然后,我们必须做出一些直觉决定是否复制数据,或信任文档并使用const_cast
。
附录1:
请注意,返回类型上的const
会阻止移动语义。据我所知,Andrei Alexandrescu首先在他的Mojo (C++03 move semantics) article Dobbs Journal博士中注意到这一点:
“ [A]
const
临时看起来像矛盾,这是一个矛盾。从实际角度来看,const
临时人员强迫在目的地复制。
所以,这是一个不使用const
的地方。
附录2:
同样地(为了支持移动语义),如果使用形式参数完成的最后一件事是在某处存储副本,那么不是通过引用传递给const
而是使用非{{{ 1}}通过值传递的参数,因为它可以简单地从。
即,而不是
const
......或优化的冗余
string stored_value;
void foo( string const& s )
{
some_action( s );
stored_value = s;
}
...考虑写一下
string stored_value;
void foo( string const& s )
{
some_action( s );
stored_value = s;
}
void foo( string&& s )
{
some_action( s );
stored_value = move( s );
}
对于左值实际参数的情况,效率可能略低,它会丢弃string stored_value;
void foo( string s )
{
some_action( s );
stored_value = move( s );
}
的优点(对代码可能做的约束),并且它打破了使用{{1}的统一约定尽可能,但它在任何情况下都不会表现不好(这是主要目标,为了避免这种情况),而且代码更小,可能更清晰。
注释:
¹标准C ++没有const
指令。此外,如果正确包含这些标头,则无法保证在全局命名空间中定义const
。
功能
答案 3 :(得分:5)
对于像这样的简短方法中的局部变量size_t length
,它并不重要。额外冗长的缺点基本上与避免错别字意外修改长度的相对安全性相平衡。做任何你当地的风格指南,或你自己的直觉告诉你。
对于更长或更复杂的方法,它可能会有所不同。但话又说回来,如果你有一个如此复杂的方法,那么你可能至少应该考虑将代码重构为更简单的部分......无论如何,如果你阅读并理解代码,那么明确的const
提供了额外的提示。有点无关紧要 - 很好但不相关。
略有关联,虽然您没有问过它:对于example
方法的参考参数,您肯定需要const
,因为您可能需要传递一个const字符串。只有当你想要禁用传递const字符串时(因为你认为你要添加代码来修改它),你应该在那里省略const
。
答案 4 :(得分:4)
我知道这么小,简单的例子真的没有显示出任何巨大的 对此有利,但似乎它对更大的人有帮助 代码库,你可能会意外地改变你不应该变量的变量 发生了变异。
问题是这基本上从未发生过。
另一方面,const
是将通过您的代码库传播的疾病,如瘟疫。只要您声明const
变量,您需要的所有内容都必须为const
,因此他们只能调用const
个函数,而且它永远不会停止。
const
并不值得为此付出代价。只有几个const
实际上保护你的情况(例如设置密钥),但即便如此,如果你不得不首先尝试这种情况,那么它是值得商榷的,并且可能不值得所有语言规则和不断的代码重复和冗余的金属学。
const
是一个很好的想法,理论上可能很好,但实际的现实是const
完全浪费时间和空间。用火烧它。
答案 5 :(得分:1)
我同意到目前为止给出的大部分答案,另一方面,某些方面仍然缺失。
定义界面时,const
关键字是您的朋友。但是你应该知道它也有些局限,有时甚至是自私的 - 这就是我所说的它的缺点。
让我们对问题进行另一次近距离观察:
标记所有不修改const的变量是否有任何缺点?`
如果您观察 您没有修改 某些内容,您可能会说它实际上是一个常量。此外,代码分析工具可以检测到这一点,即使您的编译器已经知道它。但是这种观察不应该在你身上触发 add-const reflex 。
考虑变量本身,请问
有时可以简单地删除中间变量,有时可以进行小的返工(添加或删除函数)来改进代码。
添加const
关键字可能会强化您的代码以防止其他地方的错误,也可能会对声明点的更改进行强化。
我还要添加一个关于不会改变的成员变量的词。如果您决定声明成员变量const
,则必须在包含类的构造函数的初始化列表中初始化它,这扩展了构造此类对象的前提条件。
所以不要在编译器允许的地方添加const
。不要被诱惑“石化你的代码”; - )