ChatGPT解决这个技术问题 Extra ChatGPT

对函数参数使用“const”

const 你能走多远?您只是在必要时创建函数 const 还是全力以赴并在任何地方使用它?例如,想象一个简单的 mutator,它接受一个布尔参数:

void SetValue(const bool b) { my_val_ = b; }

const 真的有用吗?我个人选择广泛使用它,包括参数,但在这种情况下,我想知道它是否值得?

我还惊讶地发现,您可以在函数声明的参数中省略 const,但可以将其包含在函数定义中,例如:

.h 文件

void func(int n, long l);

.cpp 文件

void func(const int n, const long l)

是否有一个原因?这对我来说似乎有点不寻常。

我不同意。 .h 文件也必须具有 const 定义。如果没有,那么如果将 const 参数传递给函数,编译器将生成错误,因为 .h 文件中的原型没有 const 定义。
我同意。 :-) (有问题,而不是最后一条评论!)如果不应该在函数体中更改值,这可以帮助阻止愚蠢的 == 或 = 错误,你不应该在两者中都放 const,(如果它是按值传递的,否则您必须这样做)不过,这还不足以引起争论!
@selwyn:即使您将 const int 传递给函数,它也会被复制(因为它不是引用),因此 const 并不重要。
这个问题发生了同样的争论:stackoverflow.com/questions/1554750/…
我意识到这篇文章已经有几年了,但作为一个新程序员,我很想知道这个问题,我偶然发现了这个对话。在我看来,如果一个函数不应该改变一个值,无论是引用还是值/对象的副本,它都应该是 const。它更安全,它是自记录的,而且它对调试更友好。即使对于只有一个语句的最简单的函数,我仍然使用 const。

A
Adrian Mole

当参数按值传递时, const 毫无意义,因为您不会修改调用者的对象。

错误的。

这是关于自我记录你的代码和你的假设。

如果您的代码有很多人在处理它并且您的功能不平凡,那么您应该标记 const 任何和所有可以做的事情。在编写工业级代码时,您应该始终假设您的同事是精神病患者,他们试图以任何方式得到您(尤其是因为将来通常是您自己)。

此外,正如前面有人提到的,它可能会帮助编译器优化一些东西(尽管它是一个很长的镜头)。


完全同意。这一切都是关于与人交流并将变量可以做的事情限制在应该做的事情上。
我投了反对票。我认为当您将 const 应用于简单的按值传递参数时,您会稀释您试图用 const 表示的内容。
我投了这个票。声明参数“const”会向参数添加语义信息。他们突出了代码的原始作者的意图,这将有助于随着时间的推移维护代码。
@tonylo:你误会了。这是关于在代码块(恰好是一个函数)内将局部变量标记为 const 。我会对任何局部变量做同样的事情。它与拥有一个 const 正确的 API 是正交的,这确实也很重要。
并且它可以捕获函数内部的错误——如果你知道一个参数不应该被改变,那么将它声明为 const 意味着如果你不小心修改了它,编译器会告诉你。
P
PiCTo

原因是参数的 const 仅在函数内本地应用,因为它正在处理数据的副本。这意味着函数签名无论如何都是一样的。不过,经常这样做可能是不好的风格。

我个人倾向于不使用 const,除了引用和指针参数。对于复制的对象,这并不重要,尽管它可以更安全,因为它在函数内发出意图信号。这真的是一个判断电话。我确实倾向于使用 const_iterator 虽然在循环某些东西时我不打算修改它,所以我猜每个人都有自己的想法,只要严格维护引用类型的 const 正确性。


我不能同意“坏风格”的部分。从函数原型中删除 const 的好处是,如果您决定稍后从实现部分中删除 const,则无需更改头文件。
“我个人倾向于不使用 const,除了引用和指针参数。”也许您应该澄清一下“我倾向于不在函数声明中使用多余的限定符,而是在有帮助的地方使用 const。”
我不同意这个答案。我倾向于另一种方式并尽可能标记参数const;它更具表现力。当我阅读别人的代码时,我会使用像这样的小指标来判断他们在编写代码时投入了多少精力,以及诸如幻数、注释和正确使用指针等内容。
int getDouble(int a){ ++a; return 2*a; }试试这个。当然,++a 与那里无关,但它可以在由多个程序员长时间编写的长函数中找到。我强烈建议编写 int getDouble( const int a ){ //... },它会在找到 ++a; 时产生编译错误。
这完全取决于谁需要哪些信息。您提供参数按值,因此调用者不需要知道任何您(内部)使用它做什么。所以在你的标题中写下 class Foo { int multiply(int a, int b) const; }。在您的实现中您确实关心您可以保证不会更改 ab,因此 int Foo::multiply(const int a, const int b) const { } 在这里有意义。 (旁注:调用者和实现都关心函数不改变其 Foo 对象,因此在其声明末尾的 const )
a
anatolyg

有时(太频繁了!)我必须解开别人的 C++ 代码。而且我们都知道,根据定义,其他人的 C++ 代码几乎是一团糟 :) 所以我破译本地数据流的第一件事就是将 const 放入每个变量定义中,直到编译器开始咆哮。这也意味着 const 限定值参数,因为它们只是由调用者初始化的花哨的局部变量。

啊,我希望变量默认为 const 而非 const 变量需要可变 :)


“我希望变量默认为 const” - 矛盾? 8-)说真的,“consting”一切如何帮助你解开代码?如果原始作者更改了一个假定为常量的参数,您怎么知道 var 应该是一个常量?此外,绝大多数(非参数)变量都是……变量。所以编译器应该在你开始这个过程后很快就崩溃了,不是吗?
@ysap,1. 尽可能多地标记 const 让我看到哪些部分正在移动,哪些没有。根据我的经验,许多当地人实际上是 const,而不是相反。 2.“常量变量”/“不可变变量”听起来有些矛盾,但在函数式语言和一些非函数式语言中是标准做法;参见 Rust 例如:doc.rust-lang.org/book/variable-bindings.html
在某些情况下,现在在 c++ 中也是标准的;例如,lambda [x](){return ++x;} 是一个错误;见here
Rust 中的变量默认为“const” :)
B
Ben Straub

以下两行在功能上是等效的:

int foo (int a);
int foo (const int a);

显然,如果 foo 以第二种方式定义,您将无法修改 foo 的主体中的 a,但与外部没有区别。

const 真正派上用场的是引用或指针参数:

int foo (const BigStruct &a);
int foo (const BigStruct *a);

这说明 foo 可以采用一个大参数,也许是一个千兆字节大小的数据结构,而无需复制它。此外,它对调用者说:“Foo 不会* 更改该参数的内容。”传递 const 引用还允许编译器做出某些性能决策。

*:除非它抛弃了 const-ness,但那是另一篇文章。


这不是这个问题的目的;当然,对于引用或指向的参数,使用 const 是一个好主意(如果引用或指向的值没有被修改)。请注意,指针示例中的参数不是 const ;这是参数指向的东西。
> 传递 const 引用还允许编译器做出某些性能决策。经典谬误 - 编译器必须自己确定 const-ness,由于指针别名和 const_cast,const 关键字对此无济于事
E
Enlico

从 API 的角度来看,多余的 const 是不好的:

在代码中为按值传递的内部类型参数添加多余的 const 会使您的 API 混乱,同时对调用者或 API 用户没有任何有意义的承诺(它只会妨碍实现)。

API 中不需要的太多“const”就像“哭泣的狼”,最终人们会开始忽略“const”,因为它无处不在,而且大多数时候没有任何意义。

API 中额外 const 的“reductio ad absurdum”参数对于前两点是好的,如果更多的 const 参数是好的,那么每个可以有 const 的参数都应该有 const。事实上,如果它真的那么好,你会希望 const 成为参数的默认值,并且只有在你想更改参数时才有像“mutable”这样的关键字。

因此,让我们尽可能地尝试放入 const :

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

考虑上面的代码行。不仅声明更混乱、更长、更难阅读,而且 API 用户可以安全地忽略四个“const”关键字中的三个。然而,'const' 的额外使用使得第二行可能很危险!

为什么?

对第一个参数 char * const buffer 的快速误读可能会让您认为它不会修改传入的数据缓冲区中的内存——然而,事实并非如此! 在快速扫描或误读时,多余的 'const' 可能会导致对 API 的危险和错误假设

从代码实现的角度来看,多余的 const 也是不好的:

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

如果 FLEXIBLE_IMPLEMENTATION 不为真,那么 API “承诺”不会以下面的第一种方式实现该功能。

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

这是一个非常愚蠢的承诺。为什么你要做出一个对你的调用者没有任何好处并且只会限制你的实现的承诺?

尽管这两者都是相同功能的完全有效的实现,但您所做的只是将一只手不必要地绑在背后。

此外,这是一个很容易(并且在法律上被规避)的非常肤浅的承诺。

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

看,尽管我承诺不这样做,但我还是以这种方式实现了它——只是使用了一个包装函数。这就像当坏人在电影中承诺不杀人并命令他的心腹杀死他们时一样。

那些多余的 const 只值一个电影坏蛋的承诺。

但是说谎的能力变得更糟了:

我已经了解到,您可以通过使用虚假 const 使标头(声明)和代码(定义)中的 const 不匹配。 const-happy 的拥护者声称这是一件好事,因为它允许您仅将 const 放在定义中。

// Example of const only in definition, not declaration
struct foo { void test(int *pi); };
void foo::test(int * const pi) { }

但是,反之亦然……您只能在声明中放置虚假的 const 并在定义中忽略它。这只会使 API 中多余的 const 变得更加可怕,而且是可怕的谎言——请看这个例子:

struct foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

所有多余的 const 实际上所做的只是通过强制实现者在想要更改变量或通过非 const 引用传递变量时使用另一个本地副本或包装函数来降低实现者的代码的可读性。

看看这个例子。哪个更具可读性?很明显,第二个函数中额外变量的唯一原因是因为某些 API 设计人员投入了多余的 const 吗?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

希望我们在这里学到了一些东西。多余的 const 是一个 API 杂乱无章的讨厌东西,一个烦人的唠叨,一个肤浅而毫无意义的承诺,一个不必要的障碍,并且偶尔会导致非常危险的错误。


为什么投反对票?如果您对否决票发表简短评论,这会更有帮助。
使用 const 参数的全部目的是使标记的行失败(plist = pnext)。保持函数参数不可变是一种合理的安全措施。我同意您的观点,即它们在函数声明中不好(因为它们是多余的),但它们可以在实现块中达到其目的。
@Adisak我看不出你的回答有什么问题,但从你的评论看来,你错过了重要的一点。函数定义/实现不是 API 的一部分,它只是函数声明。正如您所说,使用 const 参数声明函数是没有意义的,并且会增加混乱。然而,API 的用户可能永远不需要看到它的实现。同时,为了清晰起见,实现者可能决定对函数定义中的某些参数进行 const 限定,这很好。
@jw013 是正确的,void foo(int)void foo(const int) 是完全相同的函数,而不是重载。 ideone.com/npN4W4 ideone.com/tZav9R 这里的 const 只是函数体的一个实现细节,对重载解析没有影响。将 const 排除在声明之外,以获得更安全和整洁的 API,但如果您不打算修改复制的值,请将 const 放入 定义
@Adisak 我知道这很旧,但我相信公共 API 的正确用法是相反的。这样,从事内部工作的开发人员就不会在不应该犯的情况下犯 pi++ 之类的错误。
Q
QBziZ

const 应该是 C++ 中的默认值。像这样 :

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable

与 C 的兼容性太重要了,至少对于设计 C++ 的人来说,甚至没有考虑到这一点。
有趣的是,我从来没有想过。
同样,unsigned 应该是 C++ 中的默认值。像这样:int i = 5; // i is unsignedsigned int i = 5; // i is signed
2
2 revs

当我以编写 C++ 为生时,我尽我所能。使用 const 是帮助编译器帮助您的好方法。例如,对您的方法返回值进行 const-ing 可以使您免于打字错误,例如:

foo() = 42

当你的意思是:

foo() == 42

如果 foo() 被定义为返回一个非常量引用:

int& foo() { /* ... */ }

编译器很乐意让你为函数调用返回的匿名临时赋值。使其成为常量:

const int& foo() { /* ... */ }

消除了这种可能性。


使用什么编译器可以做到这一点? GCC 在尝试编译 foo() = 42 时出错:错误:需要左值作为赋值的左操作数
这是不正确的。 foo() = 42 与 2 = 3 相同,即编译器错误。并且返回一个 const 是完全没有意义的。它对内置类型没有任何作用。
我遇到过 const 的这种用法,我可以告诉你,最终它带来的麻烦多于好处。提示:const int foo()int foo() 的类型不同,如果您使用函数指针、信号/槽系统或 boost::bind 等东西,这会给您带来很大的麻烦。
我已更正代码以包含参考返回值。
由于返回值优化,const int& foo() 是否实际上与 int foo() 相同?
V
Void

在 comp.lang.c++.moderated here 上的旧“本周大师”文章中对此主题进行了很好的讨论。

Herb Sutter 的网站 here 上提供了相应的 GOTW 文章。


Herb Sutter 是一个非常聪明的人 :-) 绝对值得一读,我同意他的所有观点。
好文章,但我不同意他的论点。我也将它们设为 const ,因为它们就像变量一样,我从不希望任何人对我的论点进行任何更改。
G
Gabriel Staples

1. 基于我的评估的最佳答案:

根据我的评估,The answer by @Adisak 是这里的最佳答案。请注意,这个答案在一定程度上是最好的,因为它也是最有真实代码示例支持的除了使用声音和良好 -深思熟虑的逻辑。

2.我自己的话(同意最佳答案):

对于按值传递,添加 const 没有任何好处。它所做的只是:限制实现者每次想要更改源代码中的输入参数时都必须制作一个副本(这种更改无论如何都不会产生副作用,因为传入的已经是一个副本,因为它是传递的 -价值)。并且经常使用更改按值传递的输入参数来实现该功能,因此在任何地方添加 const 都会阻碍这一点。并且添加 const 会不必要地使代码到处都是 const,从而将注意力从拥有安全代码真正必要的 const 上转移开。然而,在处理指针或引用时,const 在需要时非常重要,并且必须使用,因为它可以防止函数外部持续更改带来的不良副作用,因此当参数是输入时,每个指针或引用都必须使用 const只是,不是输出。仅在通过引用或指针传递的参数上使用 const 有一个额外的好处,那就是让哪些参数是指针或引用非常明显。伸出手说“当心!任何旁边带有 const 的参数都是引用或指针!”是另一回事。我上面所描述的经常是在我工作过的专业软件组织中达成的共识,并且被认为是最佳实践。有时,规则甚至是严格的:“永远不要对按值传递的参数使用 const,但如果它们只是输入,则始终对通过引用或指针传递的参数使用它。”

3.谷歌的话(同意我的最佳答案):

(来自“Google C++ Style Guide”)

对于按值传递的函数参数,const 对调用者没有影响,因此不建议在函数声明中使用。参见 TotW #109。

既不鼓励也不鼓励对局部变量使用 const。

来源:Google C++ 风格指南的“使用 const”部分:https://google.github.io/styleguide/cppguide.html#Use_of_const。这实际上是一个非常有价值的部分,因此请阅读整个部分。

请注意,“TotW #109”代表 "Tip of the Week #109: Meaningful const in Function Declarations",也是一种有用的读法。它提供了更多信息,但对做什么的规定更少,并且基于上下文上面引用的 const 上的 Google C++ 样式指南规则,但由于它提供的清晰性,上面引用的 const 规则已添加到 Google C++ 样式指南中。

另请注意,即使我在此处引用 Google C++ 样式指南来捍卫我的立场,但这并不意味着我始终遵循指南或始终建议遵循指南。他们推荐的一些东西很奇怪,例如 their kDaysInAWeek-style naming convention for "Constant Names"然而,当世界上最成功和最有影响力的技术和软件公司之一使用与我和 @Adisak 等其他人一样的理由来支持我们在这个问题上的观点时,指出它仍然是有用和相关的。< /em>

4. Clang 的 linter,clang-tidy,对此有一些选择:

A. 还值得注意的是,Clang 的 linter clang-tidy 有一个选项 readability-avoid-const-params-in-declsdescribed here,以支持 在代码库中执行 not 使用 const 作为 pass按值函数参数

检查函数声明是否具有顶级 const 参数。

声明中的 const 值不会影响函数的签名,因此不应将它们放在那里。

例子:

无效 f(常量字符串); // 不好:const 是顶级的。无效 f(常量字符串&); // 好:const 不是顶级的。

为了完整和清晰,我添加了另外两个示例:

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

B. 它也有这个选项:readability-const-return-type - https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5. 我对此事的风格指南措辞的务实方法:

我只需将其复制并粘贴到我的样式指南中:

[复制/粘贴开始]

当它们的内容(它们指向的内容)不打算更改时,始终对通过引用或指针传递的函数参数使用 const。这样,当一个通过引用或指针传递的变量被期望改变时就变得很明显了,因为它将缺少 const。在此用例中, const 可防止函数外的意外副作用。不建议对值传递的函数参数使用 const,因为 const 对调用者没有影响:即使在函数中改变变量也不会对函数外产生副作用。有关其他理由和见解,请参阅以下资源:“Google C++ 样式指南”“const 的使用”部分“本周提示 #109:函数声明中的有意义的 const”Adisak 的 Stack Overflow 答案关于“函数参数的“使用 'const'” " "永远不要在不是定义的声明中对函数参数使用顶级 const [即:const on parameters passed by value](并注意不要复制/粘贴无意义的 const)。它是无意义的,编译器会忽略它,它是视觉噪音,可能会误导读者”(https://abseil.io/tips/109,已添加重点)。唯一对编译有影响的 const 限定符是放在函数定义中的那些,而不是那些在函数的前向声明中的,例如在头文件中的函数(方法)声明中。永远不要对函数返回的值使用顶级 const [即:按值传递的变量上的 const]。对函数返回的指针或引用使用 const 取决于实现者,因为它有时很有用。 TODO:使用以下 clang-tidy 选项强制执行上述某些操作:https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html https:// clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

以下是一些代码示例,用于演示上述 const 规则:

const 参数示例:
(部分借鉴自 here

void f(const std::string);   // Bad: const is top level.
void f(const std::string&);  // Good: const is not top level.

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

const 返回类型示例:
(有些是从 here 借来的)

// BAD--do not do this:
const int foo();
const Clazz foo();
Clazz *const foo();

// OK--up to the implementer:
const int* foo();
const int& foo();
const Clazz* foo();

[复制/粘贴结束]

关键字:在函数参数中使用const;编码标准; C 和 C++ 编码标准;编码指南;最佳实践;代码标准;常量返回值


A
Asaf R

我在作为引用(或指针)的函数参数上使用 const,这些引用(或指针)只是 [in] 数据并且不会被函数修改。意思是,当使用引用的目的是避免复制数据并且不允许更改传递的参数时。

在您的示例中将 const 放在布尔 b 参数上只会对实现施加约束,并且不会为类的接口做出贡献(尽管通常建议不要更改参数)。

函数签名为

void foo(int a);

void foo(const int a);

是一样的,这解释了你的 .c 和 .h

阿萨夫


佚名

我说 const 你的值参数。

考虑这个错误的功能:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

如果 number 参数是 const,编译器将停止并警告我们该错误。


另一种方法是使用 if(0 == number) ... else ...;
@ChrisHuang-Leaver 可怕的是,如果你像尤达那样说话:stackoverflow.com/a/2430307/210916
GCC/Clang -Wall 为您提供 -W 括号,如果这确实是您打算做的,则要求您将其设为“if ((number = 0))”。可以很好地替代尤达。
C
Community

如果您使用 ->* 或 .* 运算符,这是必须的。

它会阻止你写类似的东西

void foo(Bar *p) { if (++p->*member > 0) { ... } }

我几乎现在就这样做了,这可能不会像你想要的那样。

我想说的是

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

如果我在 Bar *p 之间放置了一个 const,编译器就会告诉我这一点。


当我要将这么多运算符混合在一起时(如果我还不知道 100%),我会立即检查运算符优先级的参考,所以 IMO 这不是问题。
我会将这 1 条困难的线分成大约 5 条或更多条清晰易读的线,每条线都有一个描述性的变量名称,使整个操作能够自我记录。所以,对我来说,这不是问题。在我看来,当可读性受到影响并且错误蔓延时将代码压缩为 1 行并不是一个好主意。
N
Nemanja Trifunovic

啊,一个艰难的。一方面,声明是一种契约,按值传递 const 参数确实没有意义。另一方面,如果您查看函数实现,如果您声明一个参数常量,就会给编译器更多优化的机会。


D
Dan Hewett

当参数按值传递时, const 毫无意义,因为您不会修改调用者的对象。

通过引用传递时应该首选 const,除非函数的目的是修改传递的值。

最后,不修改当前对象 (this) 的函数可以并且可能应该声明为 const。下面是一个例子:

int SomeClass::GetValue() const {return m_internalValue;}

这是一个不修改应用此调用的对象的承诺。换句话说,您可以调用:

const SomeClass* pSomeClass;
pSomeClass->GetValue();

如果函数不是 const,这将导致编译器警告。


L
Lloyd

标记值参数'const'绝对是一个主观的事情。

但是,我实际上更喜欢将值参数标记为 const,就像在您的示例中一样。

void func(const int n, const long l) { /* ... */ }

对我来说,价值清楚地表明函数参数值永远不会被函数改变。它们在开始时和结束时将具有相同的值。对我来说,这是保持非常函数式编程风格的一部分。

对于一个简短的函数,可以说在那里有“const”是浪费时间/空间,因为通常很明显,函数没有修改参数。

但是,对于较大的函数,它是一种实现文档的形式,并且由编译器强制执行。

我可以肯定,如果我用“n”和“l”进行一些计算,我可以重构/移动该计算,而不必担心得到不同的结果,因为我错过了一个或两个都被改变的地方。

由于它是一个实现细节,因此您不需要在标头中声明值参数 const,就像您不需要声明与实现使用的名称相同的函数参数一样。


H
HarshaXsoad

可能这不是一个有效的论点。但是如果我们在函数编译器中增加 const 变量的值,将会给我们一个错误:“错误:只读参数的增加”。所以这意味着我们可以使用 const 关键字来防止意外修改函数内部的变量(我们不应该/只读)。所以如果我们在编译时不小心做了,编译器会让我们知道。如果您不是唯一从事此项目的人,这一点尤其重要。


j
jdmichal

我倾向于尽可能使用 const 。 (或目标语言的其他适当关键字。)我这样做纯粹是因为它允许编译器进行额外的优化,否则它无法进行。因为我不知道这些优化可能是什么,所以我总是这样做,即使它看起来很傻。

据我所知,编译器很可能会看到一个 const value 参数,然后说:“嘿,这个函数无论如何都不会修改它,所以我可以通过引用传递并节省一些时钟周期。”我不认为它会做这样的事情,因为它改变了函数签名,但它说明了这一点。也许它做了一些不同的堆栈操作或其他什么......关键是,我不知道,但我知道试图比编译器更聪明只会让我感到羞耻。

C++ 有一些额外的包袱,带有 const-correctness 的想法,所以它变得更加重要。


虽然在某些情况下它可能会有所帮助,但我怀疑促进优化的可能性被夸大为 const 的好处。相反,这是在实现中陈述意图并在以后捕获想法的问题(意外地增加了错误的局部变量,因为它不是 const)。同时,我还要补充一点,非常欢迎编译器更改函数签名,因为函数可以内联,并且一旦内联,它们的整个工作方式都可以改变;添加或删除引用、制作“变量”文字等都在 as-if 规则内
L
Luke Halliwell

在您提到的情况下,它不会影响您的 API 的调用者,这就是为什么通常不这样做(并且在标题中没有必要)。它只会影响你的函数的实现。

这并不是一件特别糟糕的事情,但是考虑到它不会影响您的 API,并且它增加了类型,因此它的好处并不是那么好,因此通常不会这样做。


A
Aurélien Gâteau

我不将 const 用于传递值的参数。调用者不在乎你是否修改参数,这是一个实现细节。

真正重要的是如果方法不修改其实例,则将它们标记为 const。边做边做,否则你可能会得到很多 const_cast<> 或者你可能会发现标记一个方法 const 需要更改很多代码,因为它调用了其他应该标记为 const 的方法。

如果我不需要修改本地变量,我也倾向于标记它们。我相信它通过更容易识别“移动部件”使代码更容易理解。


s
sdcvvc

关于编译器优化:http://www.gotw.ca/gotw/081.htm


b
bpeterson76

我可以使用 const 。参数的 const 意味着它们不应该改变它们的值。这在通过引用传递时特别有价值。 const for function 声明该函数不应更改类成员。


Y
YvesgereY

总结一下:

“通常 const 传递值是无用的,充其量是误导性的。”从 GOTW006

但是您可以像处理变量一样将它们添加到 .cpp 中。

请注意,标准库不使用 const。例如 std::vector::at(size_type pos)。对标准库足够好的东西对我也有好处。


“对标准库足够好的东西对我有好处”并不总是正确的。例如,标准库一直使用像 _Tmp 这样难看的变量名——你不希望这样(实际上你不允许使用它们)。
@anatolyg 这是一个实现细节
好的,参数列表中的变量名和 const 限定类型都是实现细节。我想说的是,标准库的实现有时并不好。有时,您可以(而且应该)做得更好。标准库的代码是什么时候写的 - 10 年前? 5 年前(其中一些最新的部分)?我们今天可以编写更好的代码。
A
Attila

如果参数是按值传递的(并且不是引用),通常参数是否声明为 const 没有太大区别(除非它包含引用成员——对于内置类型来说不是问题)。如果参数是引用或指针,通常最好保护引用/指向的内存,而不是指针本身(我认为您不能将引用本身设为 const,这并不重要,因为您不能更改裁判) .保护一切你可以作为 const 的东西似乎是个好主意。如果参数只是 POD(包括内置类型)并且它们没有机会沿着道路进一步改变(例如,在您的示例中为 bool 参数),您可以省略它而不必担心出错。

我不知道 .h/.cpp 文件声明的区别,但它确实有些道理。在机器代码级别,没有什么是“const”,因此如果将函数(在 .h 中)声明为非 const,则代码与将其声明为 const 相同(抛开优化)。但是,它可以帮助您让编译器知道您不会在函数 (.ccp) 的实现中更改变量的值。当您从允许更改的接口继承时,它可能会派上用场,但您不需要更改参数来实现所需的功能。


K
Khoth

我不会把 const 放在这样的参数上——每个人都已经知道布尔值(而不是布尔值&)是常数,所以添加它会让人们认为“等等,什么?”甚至是您通过引用传递参数。


有时你想通过引用传递一个对象(出于性能原因)但不改变它,所以 const 是强制性的。保留所有这些参数 - 甚至是布尔值 - const 将是一个很好的做法,使您的代码更易于阅读。
g
gbjbaanb

使用 const 要记住的一点是,从一开始就将其设为 const 比稍后尝试将它们放入要容易得多。

当您希望某些内容保持不变时,请使用 const - 它是一个附加提示,描述了您的函数的作用和期望。我见过很多可以处理其中一些的 C API,尤其是那些接受 c-strings 的 API!

我更倾向于在 cpp 文件中省略 const 关键字而不是标题,但由于我倾向于剪切+粘贴它们,它们将被保留在两个地方。我不知道为什么编译器允许这样做,我猜它是编译器的事情。最佳实践绝对是将您的 const 关键字放在两个文件中。


我完全不明白。为什么您倾向于在 cpp 文件(函数定义)中省略它?这就是它实际上意味着什么并且可以捕获错误的地方。为什么您认为将 const 放在这两个地方是最佳做法?在头文件(函数声明)中,它没有任何意义并且使 API 混乱。也许让 decl 和 defn 看起来完全一样有一些小的价值,但在我看来,与混乱 API 的问题相比,这是一个非常小的好处。
@DonHatch 8 年后,哇。无论如何,正如 OP 所说“我也很惊讶地发现,您可以在函数声明的参数中省略 const 但可以将其包含在函数定义中”。
佚名

由于参数是按值传递的,从调用函数的角度来看,是否指定 const 没有任何区别。将按值传递参数声明为 const 基本上没有任何意义。


A
AShelly

您示例中的所有 const 都没有任何用途。默认情况下,C++ 是按值传递的,因此该函数获取这些整数和布尔值的副本。即使函数确实修改了它们,调用者的副本也不受影响。

所以我会避免额外的常量,因为

他们是多余的

他们把文本弄得乱七八糟

它们阻止我在可能有用或有效的情况下更改传入的值。


M
MarkR

确实没有理由将值参数设为“const”,因为该函数无论如何只能修改变量的副本。

使用“const”的原因是如果你通过引用传递更大的东西(例如一个有很多成员的结构),在这种情况下它确保函数不能修改它;或者更确切地说,如果您尝试以传统方式修改它,编译器会抱怨。它可以防止它被意外修改。


佚名

const 参数仅在参数通过引用(即引用或指针)传递时才有用。当编译器看到一个 const 参数时,它会确保参数中使用的变量在函数体中没有被修改。为什么有人想将按值参数设为常量? :-)


出于很多原因。制作一个按值参数 const 清楚地表明:'我不需要修改它,所以我声明它。如果我稍后尝试修改它,请给我一个编译时错误,以便我可以修复我的错误或取消标记为 const。所以这是代码卫生和安全的问题。对于添加到实现文件所需的一切,它应该是人们作为纯粹的反射做的事情,IMO。
P
PavDub

我知道这个问题“有点”过时了,但是当我遇到它时,其他人将来也可能会这样做......我仍然怀疑这个可怜的家伙会在这里列出来阅读我的评论:)

在我看来,我们仍然过于局限于 C 风格的思维方式。在 OOP 范式中,我们玩弄的是对象,而不是类型。 const 对象在概念上可能与非常量对象不同,特别是在逻辑常量的意义上(与按位常量相反)。因此,即使函数参数的 const 正确性(可能)在 POD 的情况下过于谨慎,但在对象的情况下并非如此。如果一个函数与一个 const 对象一起工作,它应该这样说。考虑以下代码片段

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

ps.:您可能会争辩说 (const) 引用在这里更合适,并给您相同的行为。嗯,对。只是给出与我在其他地方看到的不同的图片......