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

m0m*_*eni 50 c++ refactoring 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");
}
Run Code Online (Sandbox Code Playgroud)

为什么不做

size_t length = x.length();
Run Code Online (Sandbox Code Playgroud)

const喜欢

const size_t length = x.length();
Run Code Online (Sandbox Code Playgroud)

按照惯例?

我知道这么小的一个简单的例子确实没有给这个带来任何巨大的好处,但它似乎对于一个更大的代码库有帮助,你可能会意外地改变你不应该变异的变量.

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

除了必须输入5个额外字符外,还有一些缺点吗?我在这个话题上找不到太多东西,如果这是一个有这么多争议的问题,我不想在脚下射击.

Jes*_*uhl 40

标记您不修改的变量没有缺点const.

有一些上双方虽然:编译器会帮你诊断,当你无意中修改你不应该/不意味着一个变量,编译器可能(尽管由于具有语言const_castmutable这是罕见的)生成更好的代码.

所以,我建议; 使用const您可以在其中.没有缺点,您的编译器可以帮助您发现错误.没有理由不(除了一点额外打字).

请注意,这也扩展到成员函数.尽可能制作它们const- 它可以让它们在更多的上下文中使用,并帮助用户推理代码("调用此函数不会修改对象"是有价值的信息).

  • 冗长是一个缺点.它肯定会使代码的可读性降低,难以维护.例如,在OP的代码片段中,使*length**const*不会增加实际值.是的,它将保护变量免受意外修改.另一方面,如果它被修改,代码可能比意外错字更深层次的问题.这与作为API的一部分的*x*形成对比,并使*const*是该功能从其用户需要的合同合规性的重要和重要部分. (18认同)
  • @SomeWittyUsername永远不要低估一些初级维护程序员将来重构你的代码的能力,而不要注意一些变量不打算被修改的事实,不小心引入错误 - 使用`const`可以帮助阻止那些人引入错误.我已经在现实生活中看到过它比我希望的更多次...... (15认同)
  • 关于"没有缺点"的主题,如果变量有一个类类型,并且在将来的某个时候你决定从它'std :: move`,`const`将导致复制构造函数被静默调用而不是如果这些构造函数以通常的方式声明,则移动构造函数 - 分别使用`const A&`和`A &&`. (5认同)
  • 真正.但是这种错误非常罕见,应该很容易被发现.另一方面,"consting"的影响总体上是整个代码库以及所有工作的人.强调 - 我主要指的是一些简单的案例,例如这里的代码片段 (4认同)
  • 我不认为`const_cast`会阻碍优化:如果*value*已经被声明为`const`,那么试图抛弃它就是UB. (2认同)
  • @bogdan如果你已经主动将变量标记为const,你应该理解它意味着该对象是不可变的,无论你使用哪种方法来改变它,无论是显式的还是隐式的(例如通过移动语义).在这种情况下,它是一个错误而不是缺点,恕我直言. (2认同)

Mat*_* M. 19

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

  • 冗长:更多的单词,更多的符号来处理,......
  • 惯性:如果你需要修改它,你必须去除它 const

两者都值得.


冗长是一种经常听到的反对显性的论据,但是人们常常会以理解速度来误读速度.在冗长和显性之间存在平衡,当然,过于冗长可能会淹没有用信息,但过于隐含/简洁可能不会呈现必须重建/推断/推断/的信息.

就个人而言,我使用强类型静态检查语言,以便编译器尽早发现我的错误; 注释const用于向读者编译器提供信息.我认为值得额外的6个符号.

至于惯性,删除const可能只是改变的一小部分......它通过强迫你经历所有使用它的地方来回报自己并查看周围的代码以确保它实际上可以删除它const.突然修改先前不可变的代码路径中的特定数据片段需要确保代码路径(或其调用者)的任何部分都不会意外地依赖于这种不变性.

  • 随着代码的变化,如果事实证明您不需要它,那么稍后删除 `const` 要容易得多,如果事实证明您确实应该清楚常量性,则添加它要容易得多。在我看来,简单声明中的 `const` 是为它传达给读者的信息支付的一小笔费用。当 `const` 存在时,我知道创建临时变量只是为了给表达式一个有意义的名称,而不是因为它是一个会随着时间而改变的属性,并且可能必须遵守一些不明显的不变量。 (2认同)

Che*_*Alf 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");
}
Run Code Online (Sandbox Code Playgroud)

......我写这个:

#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" ); }
Run Code Online (Sandbox Code Playgroud)

const相对于原始代码,我可以添加的主要位置ch是循环中的变量.我觉得这很好.const通常是可取的,因为它减少了必须考虑的可能的代码操作,并且基于范围的循环可以让您拥有更多const.

使用const大多数东西的主要缺点是,当你必须与C API相关时.

然后,只需要做出一些直觉决定是否复制数据,或信任文档并使用const_cast.


        附录1:
请注意,const返回类型会阻止移动语义.据我所知,Andrei Alexandrescu在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;
}
Run Code Online (Sandbox Code Playgroud)

......或优化的冗余

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 );
}
Run Code Online (Sandbox Code Playgroud)

......考虑一下写作

string stored_value;

void foo( string s )
{
    some_action( s );
    stored_value = move( s );
}
Run Code Online (Sandbox Code Playgroud)

它可以是左值实际参数的情况下稍微低效率的,它丢弃的优势const(上什么该代码可能会做的约束),并且它打破了使用统一的约定const只要有可能,但它并没有任何表现不佳情况(这是主要目标,以避免这种情况),它是更小,可能更清晰的代码.


注意:
¹标准C++没有#import指令.此外,如果正确包含这些标头,则无法保证size_t在全局命名空间中定义这些标头.

  • 没有人会指出尾随返回类型(通常应该是什么)`int main()`或在"首选"方法中使用`using namespace std`的无意义? (14认同)
  • "auto main() - > int" - 真的吗? (7认同)
  • 我没有声称好的约定毫无意义,我断言你的*不遵循*一个好的约定是没有意义的(充其量,最坏的情况下适得其反).UNS的论点是一匹死马; 我会在分歧的地方留下分歧."首选"是一种消除歧义,以简化我所谓的"您编写的代码". (4认同)
  • 一般来说,我将变量放在一个for-for循环中的`const&`.对于某些类型而言无关紧要,对于某些类型,它避免了昂贵的副本.因此,除非你明确地*想要/需要*一个cooy,`const&`似乎是理智的默认.. (3认同)
  • @JesperJuhl但在这种情况下,`const&`可能会减慢循环速度.复制单个`char`可能比解除引用更快. (2认同)
  • @ Cheersandhth.-Alf我知道语法的含义,没有理由在这里使用它. (2认同)

hyd*_*yde 5

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

对于更长或更复杂的方法,它可能会有所不同.但话又说回来,如果你有一个如此复杂的方法,那么你可能至少应该考虑将代码重构为更简单的部分......无论如何,如果你阅读并理解代码,那么显式提供的额外提示const有点无关紧要 - 很好但无关紧要.


稍微相关,虽然您没有问过它:对于您的example方法的参考参数,您肯定需要const,因为您可能需要传递一个const字符串.只有当你想要禁用传递const字符串时(因为你认为你将添加代码来修改它),你应该省略const它.