标记所有不修改 const 的变量有什么缺点吗?
Are there any downsides to marking all variables you don't modify const?
经过大量的谷歌搜索,我发现了很多关于将函数及其参数标记为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像
const size_t length = x.length();
按照惯例?
我知道这样一个小而简单的例子并没有显示出任何巨大的好处,但是在一个更大的代码库中,当你可能不小心改变了一个你不应该改变的变量时,它似乎会很有帮助。
尽管有这样的好处,但我并没有看到它被使用得那么多(在我所见过的c++代码库中),也没有像创建函数及其参数const
那样被提及。
除了必须输入5个额外的字符之外,这样做还有什么缺点吗?我在这个话题上没有找到很多东西,如果有这么多问题,我不想搬起石头砸自己的脚。
标记不修改const
的变量没有缺点。
也有一些好处:当你无意中修改了一个你不应该或不想修改的变量时,编译器将帮助你诊断,编译器可能(尽管由于语言中有const_cast
和mutable
,这种情况很少见)生成更好的代码。
所以,我建议;尽可能使用const
。没有缺点,编译器可以潜在地帮助您发现错误。没有理由不这样做(除了一些额外的输入)。
注意,这也扩展到成员函数。尽可能将它们设置为const
——它可以让它们在更多的上下文中使用,并帮助用户对代码进行推理("调用该函数不会修改对象"是有价值的信息)。
我能想到至少两个缺点:
- 冗长:更多的单词,更多的符号要处理,…
- 惯性:如果你需要修改它,你必须去删除这个
const
和两者都值得。
冗长是人们经常听到的反对明确的论点,然而人们经常把阅读速度和理解速度混为一谈。在冗长和明确之间需要找到一个平衡,当然,过于冗长可能会淹没有用的信息,但过于含蓄/简洁可能不会呈现需要重构/推断/演绎的信息。
就我个人而言,我使用强类型静态检查语言,以便编译器尽早找出我的错误;使用const
进行注释既向读者提供信息,也向编译器提供信息。我认为它值得额外的6个符号。
至于惯性,删除const
可能只是改变的一小部分成本…它通过强迫你浏览所有使用它的地方并检查周围的代码来回报自己,以确保删除这个const
是正确的。突然修改代码路径中某个特定的数据片段(以前是不可变的)需要确保代码路径(或其调用者)的任何部分都不会意外地依赖于这种不可变性。
代替这个¹非标准代码:
#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在Dr Dobbs Journal上的Mojo (c++ 03移动语义)文章中首次提到的:
” [A]
const
临时看起来像是一个矛盾修饰法,一个术语上的矛盾。从实际的角度来看,const
临时强制在目的地复制。
所以,这是一个应该而不是使用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
。一口>
对于像这样的短方法中的局部变量size_t length
,这并不重要。额外冗长的缺点基本上与避免错别字意外修改长度的相对安全性相平衡。按照当地的风格指南去做,或者你自己的直觉告诉你。
对于更长的或更复杂的方法,它可能是不同的。但话又说回来,如果你有一个如此复杂的方法,也许你至少应该考虑将你的代码重构成更简单的部分……无论如何,如果你阅读并理解代码,显式const
提供的额外提示有点无关紧要-很好,但无关紧要。
稍微相关,虽然你没有问它:对于example
方法的引用参数,你肯定想要const
,因为你可能需要传递一个const string。只有当您想要禁用传递const string(因为您认为您将添加代码来修改它)时,才应该省略const
。
我知道这样一个小的,简单的例子真的没有显示任何巨大这是有益的,但它似乎对更大的在代码库中,您可能会意外地更改不应该更改的变量有突变。
问题是这基本上从来没有发生过。
另一方面,const
是一种疾病,将像瘟疫一样在你的代码库中传播。一旦你声明了一个const
变量,你需要的所有东西都必须是const
,所以它们必须只调用const
函数,而且它永远不会停止。
在绝大多数情况下,const
根本不值你为它付出的代价。只有少数情况下const
可以真正保护你(例如set keys),但即便如此,如果你一开始就必须是一个完全愚蠢的人去尝试它,这也是值得商榷的,而且可能不值得所有的语言规则和不断的代码复制和冗余的元逻辑。
const
是一个很好的想法,理论上可能很好,但实际情况是const
完全是浪费时间和空间。
我同意到目前为止给出的大多数答案,但另一方面,有些方面仍然缺失。
在定义接口时,const
关键字是您的朋友。但你应该知道,它也有一定的局限性,有时甚至是自私的——这就是我所说的它的缺点。
让我们再仔细看一下问题:
标记所有不修改const的变量有什么缺点吗?'
如果你观察你没有修改的东西,你可以说它实际上是一个常数。代码分析工具也可以检测到这一点,甚至你的编译器也已经知道了。但是这个观察结果不应该触发你的add-const反射。
而不是考虑变量本身,问
- 它到底有用吗?
- 它也打算是常数吗?
有时可以简单地删除中间变量,有时可以做一个小的返工(添加或删除一个函数)来改进代码。
添加const
关键字可以增强代码的安全性,防止其他地方的错误,但也可以防止声明点的更改。
让我再补充一点关于不改变的成员变量。如果你决定声明一个成员变量const
,你必须在包含它的类的构造函数的初始化列表中初始化它,这扩展了构造该类对象的前提条件。
所以不要在编译器允许的地方添加const
。不要被引诱去"石化你的代码";-)
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 在 .h 文件中的类中声明静态变量和在.cpp文件中声明"global"变量有什么区别
- 未初始化的变量有什么危险
- 在C/C++中将变量名定义为__00000001有什么好处吗
- 我可以在这里替换什么,因为我不能在 C# 中使用隐式变量的 lambda 函数?
- Visual C++: MSVC vs. GCC+CLANG: 处理 lambda 捕获类成员变量,正确的方法是什么?
- 存储变量的更有效方法是什么?
- 变量 BitMask 在函数 CeilLog2 中的实际效果是什么?
- 这个变量在 C++ 中的范围是什么?
- 从二进制流中读取时,将双精度变量的地址转换为 char* 意味着什么?
- 变量按什么顺序相乘
- 与普通变量相比,仅仅读取原子变量的性能有什么不同吗
- 使用 gtest 框架在单元测试代码中检查目标对象的私有变量的最佳实践是什么?
- C++在变量的内存地址上做什么来"deallocate"它?
- 有什么方法可以使用 int 变量来完成组件名称吗?
- 从"LLONG_MAX 秒"构造 std::chrono::毫秒变量时发生了什么?
- 将共享指针传递给函数参数 - 将其分配给局部变量的正确方法是什么
- 正在连接的等待条件变量的线程会发生什么情况?
- 当我不确定输入是什么时,使用什么变量?
- 对于非常大的整数,什么变量类型