标记所有你不修改const的variables是否有缺点?

经过大量的search之后,我发现很多有关将函数和它们的参数标记为const ,但没有将variables标记为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像

 const size_t length = x.length(); 

按照惯例?

我知道这样一个简单的例子实际上并没有显示出任何巨大的好处,但似乎在更大的代码库中可能会有帮助,在这种情况下,您可能会意外地改变一个您不应该发生变异的variables。

尽pipe有这样的好处,但我并没有真正看到它使用了那么多(在我见过的C ++代码库中)或者提到了几乎和函数和它们的参数一样。

除了input5个额外的字符之外,还有其他的缺点吗? 我对这个话题没有多less发现,如果这个问题有那么多的常量,我不想在脚下自</s>身亡。

标记你不修改constvariables没有缺点。

不过有一些方面:编译器会帮助你诊断,当你无意中修改一个你不应该/不想要的variables的时候,编译器可能会 (虽然由于const_castmutable的语言很less)码。

所以,我build议; 使用const你可以。 没有缺点,您的编译器可能会帮助您发现错误。 没有理由不(除了一点额外的打字)。

请注意,这也扩展到成员函数。 尽可能使它们成为const – 它允许它们在更多的上下文中使用,并且帮助用户推理代码(“调用这个函数不会修改对象”是有价值的信息)。

我可以想到至less两个缺点:

  • 详细程度:更多的话,处理更多的符号,…
  • 惯性:如果你需要修改它,你必须去除这个const

而且都是值得的。


冗长是一个经常听到的反对明确性的论点,然而人们常常在理解速度的时候错误的阅读速度。 在冗长和清晰之间有一个平衡,当然,太冗长可能会淹没有用的信息,但是隐含/简洁也许不能提供必须重构/推断/推导的信息。

就个人而言,我使用强types的静态检查语言,以便编译器尽早找出错误; 用const注释是给读者编译器提供信息。 我认为它值得额外的6个符号。

至于惯性,去除const可能只是一个很小的变化成本…它通过迫使你遍历所有使用它的地方来回报自己,并且检查周围的代码,以确保它实际上可以移除这个const 。 突然修改以前不可变的代码path中的特定数据片段需要确保代码path(或其调用者)的任何部分意外地依赖于该不变性。

而不是这个非标准的代码:

 #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的主要地方,相对于原来的代码,是循环中的chvariables。 我认为这很好。 const通常是可取的,因为它减less了需要考虑的可能的代码操作,而基于范围的循环让你拥有更多的const

在大多数情况下使用const的主要缺点是必须与C API相关联。

那么就需要做一些关于是否复制数据的直觉判断,或者信任文档并使用const_cast


附录1:
请注意,返回types的const可以防止移动语义。 据我所知,这是Andrei Alexandrescu在Dobbs Journal博士的Mojo(C ++ 03移动语义学)文章中首次提到的:

[ const ]临时性看起来像一个矛盾,矛盾的方面。 从实践的angular度来看, const temporaries强制在目的地复制。

所以,这是一个应该使用const

对不起,我忘了提到这本来的; 用户bogdan对另一个答案的评论提醒了我。


附录2:
同样的道理(为了支持移动语义),如果用正式参数完成的最后一件事情是在某处存储一个副本,那么不用通过引用来传递const而是使用通过值传递的非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 ); } 

对左值的实际参数来说,它的效率可能会稍微低一点,它会放弃const的优点(约束代码可能做的事情),并且在可能的地方打破了使用const的统一约定,但是它不能很好地执行在任何情况下(这是主要目标,以避免这种情况),它是更小,可能更清晰的代码。


备注
¹标准C ++没有#import指令。 另外,如果包含这些头文件,则不能保证在全局名称空间中定义size_t

对于像这样的简短方法中的局部variablessize_t length ,它并不重要。 额外的冗长度的缺点基本上与避免错别字意外修改长度的相对安全性相平衡。 做任何你当地的风格指南,或者你自己的直觉告诉你。

对于更长或更复杂的方法,可能会有所不同。 但是如果你有一个非常复杂的方法,那么你应该至less考虑将你的代码重构成更简单的代码……无论如何,如果你阅读和理解代码,显式const提供的额外提示是不相干的 -好但不相干。


稍微相关,虽然你没有问:关于你的example方法的引用参数,你一定要const ,因为你可能需要传递一个conststring。 只有当你想禁用传递conststring(因为你认为你会添加代码来修改它),你应该省略const

我知道这样一个简单的例子实际上并没有显示出任何巨大的好处,但似乎在更大的代码库中可能会有帮助,在这种情况下,您可能会意外地改变一个您不应该发生变异的variables。

问题是,这基本上从来没有实际发生。

另一方面, const是一种像瘟疫一样通过代码库传播的疾病。 只要你声明一个constvariables,你所需要的所有东西都必须是const ,所以他们只能调用const函数,它永远不会停止。

在无限的大多数情况下, const并不值得你付出代价。 只有一些情况下, const才能真正保护你(例如设置键),但即使如此,如果你必须首先尝试这种方法,那么这是值得商榷的,也许不值得所有的语言规则和不间断的代码重复和冗余metalogic。

const是一个不错的主意,理论上可能不错,但实际情况是const是浪费时间和空间。 用火烧掉。

我同意迄今为止给出的大部分答案,另一方面,有些方面仍然缺失。

定义接口时, const关键字是你的朋友。 但是你应该知道,这也是有限的,有时甚至是自私的 – 这就是我所说的缺点。

让我们再仔细看看这个问题:

是否有任何缺点标记所有你不修改const的variables?

如果你注意到 你不修改某些东西,你可能会说它是一个不变的东西。 此外,代码分析工具可以检测到这一点,即使你的编译器已经知道它。 但是这个观察不应该触发你的附加常量reflection

反而想想variables本身,问

  • 它有用吗?
  • 它也是打算保持不变?

有时可以简单地删除一个中间variables,有时可以通过一个小的返工(添加或删除一个函数)来改善代码。

添加const关键字可能会使您的代码在其他位置出现错误,而且还会针对声明点处的更改加以强化。

我还要添加一个关于不会改变的成员variables。 如果你决定声明一个成员variablesconst ,你必须在包含类的构造函数的初始化列表中初始化它,这扩展了构造这个类的对象的前提条件。

所以不要在编译器允许的地方添加const 。 不要被引诱“石化你的代码”;-)