将'const'用于功能参数


396

你走多远const?您只是const在必要时制作函数,还是全力以赴,并在各处使用它?例如,假设有一个简单的变量,它带有一个布尔值:

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定义。
selwyn

10
我同意。:-)(提出问题,而不是最后一个注释!)如果不应该在函数主体中更改值,则可以帮助阻止傻子==或= bug,您永远不要在两个变量中都使用const,(如果它是通过值传递的,您必须以其他方式传递)尽管还不足以引起关于它的争论!
克里斯·黄·利弗

19
@selwyn:即使您将const int传递给该函数,它也会被复制(因为它不是引用),因此const-ness无关紧要。
jalf

1
在这个问题上发生了同样的辩论:stackoverflow.com/questions/1554750/…– 2009
部分

5
我意识到这篇文章已有两年历史了,但是作为一名新程序员,我想知道这个问题,因此偶然发现了这个对话。我认为,如果函数不应更改值,无论是引用还是值/对象的副本,则应为const。它更安全,具有自记录功能,并且调试友好。即使对于具有一个语句的最简单的函数,我仍然使用const。

Answers:


187

原因是参数的const仅在函数内本地应用,因为它正在处理数据的副本。这意味着函数签名实际上是相同的。经常这样做可能是不好的风格。

我个人倾向于不使用const,除了引用和指针参数。对于复制的对象,这并不重要,尽管它可以更安全,因为它表示函数中的意图。这确实是一个判断电话。我确实倾向于使用const_iterator,尽管在循环执行某些操作时,我并不打算对其进行修改,因此,只要严格维护引用类型的const正确性,我就可以猜测每个人自己的情况。


57
我不同意“坏风格”部分。const从函数原型中删除的好处是,如果您决定const稍后从实现部分中删除,则无需更改头文件。
米哈尔戈尔诺-

4
“我个人倾向于不使用const,除了引用和指针参数。” 也许您应该澄清一下,“我倾向于在函数声明中不要使用多余的限定词,而应在有明显作用的const地方使用”。
Deduplicator 2014年

3
我不同意这个答案。我采用另一种方法,并const尽可能标记参数。更具表现力。当我阅读别人的代码时,我会使用类似这样的小指标来判断他们在编写代码时需要多加小心,例如魔术数字,注释和正确的指针用法等
Ultimater

4
int getDouble(int a){ ++a; return 2*a; }尝试这个。当然,++a它在那里没有任何关系,但是可以在一个以上的程序中找到,该函数由一个以上的程序员在很长一段时间内编写而成。我强烈建议编写int getDouble( const int a ){ //... }该代码,该代码将在发现时产生编译错误++a;
dom_beau

3
这完全取决于谁需要哪些信息。您可以按值提供参数因此调用方不需要(在内部)对您的操作有任何了解。所以写class Foo { int multiply(int a, int b) const; }在你的头文件中。在您的实现中,您确实会保证不会改变ab因此int Foo::multiply(const int a, const int b) const { }在这里很有意义。(旁注:调用者和实现都关心函数不会更改其Foo对象的事实,因此不会在声明的末尾更改const)
CharonX

415

“当参数通过值传递时,const是没有意义的,因为您将不会修改调用者的对象。”

错误。

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

如果您的代码中有很多人在使用它,而您的函数很简单,那么您应该在所有可能的地方标上“ const”。在编写具有行业实力的代码时,您应该始终假设您的同事是精神病患者,他们想尽一切可能帮助您(特别是因为将来通常是您自己)。

此外,正如前面提到的那样,它可能会帮助编译器稍微优化一些东西(尽管这是一个漫长的过程)。


41
完全同意。这是所有与人沟通并限制变量可以完成的工作的范围。
Len Holgate

19
我把这个投了反对票。我认为当您将const应用于简单的按值传递参数时,会淡化您试图用const表示的内容。
tonylo

26
我已经投票赞成这一点。声明参数“ const”将语义信息添加到参数中。它们突出了代码的原始作者的意图,这将有助于随着时间的流逝维护代码。
理查德·科登

13
@tonylo:你误会了。这是关于在代码块(恰好是函数)中将局部变量标记为const。对于任何局部变量,我都会这样做。它与具有const正确的API正交,这确实也很重要。
rlerallut

56
而且它可以捕获函数内部的错误-如果您知道不应更改参数,则将其声明为const意味着编译器会告诉您是否意外修改了它。
阿德里安

156

有时(太频繁了!)我必须解开别人的C ++代码。我们都知道,别人的 C ++代码几乎完全是按定义定义的:)因此,解密本地数据流的第一件事是将const放入每个变量定义中,直到编译器开始吠叫为止。这也意味着const限定值参数,因为它们只是调用方初始化的漂亮局部变量。

嗯,我希望变量是常量默认和可变被要求对非const变量:)


4
“我希望变量默认为const”-一个矛盾的词?8-)认真地说,如何“常量化”所有内容来帮助您解开代码?如果原始作者更改了一个假定为常量的参数,那么您怎么知道var应该是常量呢?此外,绝大多数(非自变量)变量都是...变量。因此,在您启动该过程后,编译器应该很快就会中断,不是吗?
ysap

8
@ysap,1.尽可能多地标记const,让我看到哪些部分在运动,哪些不在。根据我的经验,许多本地人实际上是常量,而不是相反。2.“常量” /“不可变”听起来像是矛盾词,但在功能语言和一些非功能语言中是标准惯例;参见Rust,例如:doc.rust-lang.org/book/variable-bindings.html
Constantin

1
现在在某些情况下也是c ++的标准;例如,lambda [x](){return ++x;}是错误;看到这里
anatolyg

10
constRust中,变量默认为“ ” :)
凤凰城

变量不一定是可分配的,以允许它们进行更改;它们初始化时使用的值在运行时也可能有所不同。
Sphynx

80

以下两行在功能上等效:

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

显然,您无法a在主体中进行修改(foo如果它是用第二种方式定义的),但与外部没有任何区别。

const真正派上用场参照或指针参数:

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

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

*:除非它消除了常量性,但这是另一篇文章。


3
这不是这个问题。当然,对于引用或指向的参数,最好使用const(如果未修改引用或指向的值)。注意,在您的指针示例中,不是const 的参数。参数所指向的就是它。
tml

>传递const引用还可以使编译器做出某些性能决定。经典谬误-编译器必须自行确定const-ness,这要归功于指针别名和const_cast,const关键字对此无济于事
jheriko

73

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

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

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

API中额外的const的“ reducio 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不为true,则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
class foo { void test(int *pi); };
void foo::test(int * const pi) { }

但是,反之亦然。您可以仅在声明中添加伪常量,而在定义中忽略它。这只会使API中的多余const变得更可怕,更可怕-参见以下示例:

class 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显得杂乱无章,令人讨厌的烦恼,浅薄而毫无意义的承诺,不必要的障碍,并偶尔会导致非常危险的错误。


9
为什么要下票?如果您对下一篇投票发表简短评论,将会更有帮助。
阿迪萨克

7
使用const参数的全部目的是使标记的行失败(plist = pnext)。保持函数参数不变是一种合理的安全措施。我确实同意您的观点,即它们在函数声明中不好(因为它们是多余的),但是它们可以在实现块中达到其目的。
touko

23
@Adisak我的答案本身没有发现任何问题,但是从您的评论看来,您缺少重要的一点。函数定义/实现不是 API的一部分,API只是函数声明。如您所说,用const参数声明函数是没有意义的,并且会增加混乱。但是,API的用户可能永远不需要查看其实现。同时,为了清楚起见,实现者可以决定仅对函数定义中的某些参数进行const限定,这非常好。
jw013

17
@ jw013是正确的,void foo(int)并且void foo(const int)是完全相同的函数,而不是重载。ideone.com/npN4W4 ideone.com/tZav9R此处的const只是功能主体的实现细节,对重载解析没有影响。对于更安全,更整洁的API,请将const保留在声明之外,但如果您不打算修改复制的值,则将const放入定义中
Oktalist 2014年

3
@Adisak我知道这已经很老了,但我相信正确的使用公共API的方法会相反。这样,从事内部工作的开发人员就不会犯错误,例如pi++本来应该做的事情。
CoffeeandCode 2014年

39

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

int i = 5 ; // i is a constant

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

8
正是我的想法。
康斯坦丁

24
至少对于设计C ++的人来说,与C的兼容性太重要了,以至于不考虑这一点。

4
有趣的是,我从未想过。

6
同样,unsigned应该是C ++中的默认设置。像这样:int i = 5; // i is unsignedsigned int i = 5; // i is signed
hkBattousai

25

当我用C ++编码谋生时,我便尽了一切可能。使用const是帮助编译器帮助您的好方法。例如,构造方法的返回值可以使您免于输入错误,例如:

foo() = 42

当你的意思是:

foo() == 42

如果定义了foo()以返回非常量引用:

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

编译器将很高兴让您为函数调用返回的匿名临时变量分配一个值。使它成为常量:

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

消除这种可能性。


6
用什么编译器工作?GCC尝试编译时foo() = 42出现错误:错误:左值必须作为赋值的左操作数
gavrie 2010年

这是不正确的。foo()= 42与2 = 3相同,即编译器错误。并且返回const完全没有意义。对于内置类型,它不做任何事情。
乔什(Josh)

2
我曾经遇到过const的用法,我可以告诉你,最终它带来的麻烦多于收益。提示:const int foo()与的类型不同int foo(),如果您使用函数指针,信号/插槽系统或boost :: bind之类的东西,则会给您带来麻烦。
Mephane 2011年

2
我已更正代码以包含参考返回值。
2011年

由于返回值的优化,const int& foo()实际上与并不相同int foo()吗?
Zantier 2014年

15

有关于这一主题在comp.lang.c文章++老“本周的大师”商量好了。主持这里

相应的GOTW文章可在Herb Sutter的网站上找到


1
赫伯·萨特(Herb Sutter)是一个非常聪明的人:-)绝对值得一读,我同意他的所有观点。
阿迪萨克2012年

2
好文章,但我不同意他的说法。我也将它们设为const,因为它们就像变量一样,而且我永远不希望有人对我的参数进行任何更改。
QBziZ 2012年

9

我说const是您的值参数。

考虑以下越野车功能:

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

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


2
另一种方法是使用if(0 == number)... else ...;
Johannes Schaub-litb

5
@ ChrisHuang-Leaver可怕的不是,如果像尤达那样说话,您会这样做:stackoverflow.com/a/2430307/210916
MPelletier 2012年

GCC / Clang -Wall给您-Wparentheses,如果确实要这样做,则要求将其设为“ if((number = 0))”。可以很好地替代Yoda。
Jetski S型

8

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

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

的功能签名

void foo(int a);

void foo(const int a);

一样,这说明了您的.c和.h

阿萨夫


6

如果使用->*or .*运算符,则必须这样做。

它阻止您编写类似

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

我现在几乎做了,而这可能并没有达到您的预期。

我想说的是

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

如果我constBar *和之间插入一个p,编译器会告诉我的。


4
当我要混合许多运算符(如果我还不知道100%)时,我将立即检查关于运算符优先级的参考,因此IMO这不是问题。
mk12 2012年

5

啊,一个艰难的人。一方面,声明是一个契约,通过值传递const参数确实没有任何意义。另一方面,如果您看一下函数的实现,则在声明参数常量的情况下,可以给编译器更多的优化机会。


5

当参数通过值传递时,const是没有意义的,因为您将不会修改调用者的对象。

通过引用传递时最好使用const,除非函数的目的是修改传递的值。

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

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

这是保证不会修改此调用所应用到的对象。换句话说,您可以致电:

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

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


5

将值参数标记为“ const”绝对是主观的。

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

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

对我来说,该值清楚地表明该函数从未更改过函数参数值。它们在开始时和结束时将具有相同的值。对我来说,这是保持一种非常实用的编程风格的一部分。

对于一个简短的函数,在其中放置“ const”无疑是浪费时间/空间,因为通常很明显参数没有被函数修改。

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

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

由于它是实现的详细信息,因此您无需在标头中声明值参数const,就像您不需要使用实现所用的名称来声明函数参数一样。


4

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


3

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

就我所知,编译器很可能会看到一个const值参数,并说:“嘿,此函数无论如何都不会对其进行修改,因此我可以按引用传递并节省一些时钟周期。” 我认为它永远不会做这样的事情,因为它改变了函数签名,但是它说明了这一点。也许它执行了一些不同的堆栈操作或其他操作……要点是,我不知道,但是我知道尝试变得比编译器更聪明只会导致我感到羞耻。

C ++有一些额外的负担,带有const-correctness的思想,因此它变得更加重要。


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

3

1.根据我的评估的最佳答案:

根据我的评估,@ Adisak的答案是最好的答案。请注意,这个答案是一部分最好的,因为它也是最知名的备份与真正的代码示例,除了使用声音和深思熟虑出来的逻辑。

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

  1. 对于按值传递,添加不会带来任何好处const。它所做的就是:
    1. 限制了实现者每次想要在源代码中更改输入参数时都必须进行复制(更改不会有任何副作用,因为传递的值已经通过了传递,因此传递的已经是副本)。通常,更改通过值传递的输入参数可用于实现该功能,因此const在任何地方添加都可能会阻止此操作。
    2. constconst所有地方不必要地添加带有s 的代码,从而将注意力从const具有安全代码真正必要的s上移开。
  2. 但是,在处理指针引用时const在需要时必须使用它,这一点至关重要,因为它可以防止函数外部发生持久变化而产生不希望的副作用,因此,当parm仅是输入时,必须使用每个单独的指针或引用const,不是输出。const 在通过引用或指针传递的参数上使用还具有另一个好处,即可以使您真正清楚地知道哪些参数是指针或引用。坚持并说“当心!const旁边的任何参数都是引用或指针!”是另一回事。
  3. 我上面描述的内容经常是我工作过的专业软件组织中达成的共识,并被认为是最佳实践。有时甚至是严格的规则:“永远不要在通过值传递的参数上使用const,而仅在引用或指针(仅作为输入)传递的参数上使用它”。

3. Google的话(同意我和最佳答案):

(摘自《Google C ++样式指南》)

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

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

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

请注意,“ TotW#109”代表“本周技巧#109:const在函数声明中有意义”,这也是有用的读物​​。它对操作的信息更丰富,说明性更小,并且基于上下文该上下文const上文引用的《 Google C ++样式指南》规则,但由于其提供的清晰性,const上文引用的规则已添加到Google C ++时尚指南。

还要注意,即使我在这里引用Google C ++样式指南来捍卫自己的立场,也并不意味着我总是遵循该指南或始终建议遵循该指南。他们推荐的某些内容很奇怪,例如kDaysInAWeek“常量名称” 的样式命名约定但是,指出世界上最成功,最有影响力的技术和软件公司之一使用与我和@Adisak等其他公司相同的理由来支持我们在此问题上的观点仍然是有用且有意义的。

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

A.这也是值得注意的是,铛的棉短绒,clang-tidy,有一个选项,readability-avoid-const-params-in-decls这里描述的,以支持在一个代码库执行使用const对于传值函数的参数

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

声明中的const值不会影响函数的签名,因此不应将其放在此处。

例子:

void f(const string);   // Bad: const is top level.
void f(const 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]

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

5.对于如何在此问题上撰写风格指南,我采取务实的态度:

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

[复制/粘贴开始]

  1. const当不希望更改其内容(指向的内容)时,请始终对通过引用或指针传递的函数参数使用。这样,当期望更改由引用或指针传递的变量时,它会变得很明显,因为它将缺少const。在这种使用情况下,const可以防止功能外的意外副作用。
  2. 这是不建议在使用const上按值传递函数的参数,因为const对来电者没有影响:即使变量是在函数变化会有以外的功能无副作用。请参阅以下资源,以获取更多依据和见解:
    1. “ Google C ++样式指南”“使用const”部分
    2. “每周技巧109:const在函数声明中有意义”
    3. Adisak关于“使用'const'作为函数参数”的堆栈溢出答案
  3. 永不使用顶层const[即:const对参数值传递 ]在对函数参数的声明不属于定义(并小心不要复制/粘贴一个无意义的const。)它是没有意义的,并通过编译器忽略,这是视觉噪声,可能会误导读者”(https://abseil.io/tips/109,添加了重点)。
    1. 唯一const会影响编译的限定符是放在函数定义中的限定符,而不是放在函数的前向声明中的限定符,例如在头文件中的函数(方法)声明中。
  4. 切勿在函数返回使用顶级const[即:const在值传递的变量上 ] 。
  5. 使用const上的函数返回的指针或引用是由实现,因为它有时是有用的。
  6. 待办事项:使用以下clang-tidy选项强制执行上述某些操作:
    1. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
    2. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

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

const参数示例:(
有些是从这里借来的)

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返回类型示例:(
有些是从这里借来的)

// 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();

[复制/粘贴结束]


2

在您提到的情况下,它不会影响您的API的调用者,这就是为什么它不常用(并且在标头中没有必要)的原因。它仅影响功能的实现。

这样做并不是一件坏事,但是考虑到它不会影响您的API并带来键入的好处,因此带来的好处并不是很大,因此通常不会这样做。


2

我不对值传递的参数使用const。调用者不在乎是否修改参数,这是实现细节。

真正重要的是,如果方法不修改其实例,则将它们标记为const。按需进行此操作,因为否则可能会导致很多const_cast <>结束,或者可能会发现标记方法const需要更改大量代码,因为它调用了应标记为const的其他方法。

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



2

我可以使用const。参数常量意味着它们不应更改其值。当通过引用传递时,这尤其有价值。函数的const声明该函数不应更改类成员。


2

总结一下:

  • “通常const传递值是毫无用处的,而且充其量只能是误导。” 从GOTW006
  • 但是您可以像使用变量一样将它们添加到.cpp中。
  • 请注意,标准库不使用const。例如std::vector::at(size_type pos)。标准库足够好对我也有好处。

2
“对标准库足够好对我有好处”并不总是正确的。例如,标准库_Tmp始终像丑陋的变量名一样使用-您不希望这样做(实际上您不允许使用它们)。
anatolyg

1
@anatolyg,这是一个实现细节
Fernando Pelliccioni

2
好的,参数列表中的变量名和const限定类型都是实现细节。我想说的是,标准库的实现有时不好。有时,您可以(并且应该)做得更好。标准库的代码是什么时候写的-10年前?5年前(其中的一些最新部分)?我们今天可以编写更好的代码。
anatolyg

1

如果参数是通过值(而不是引用)传递的,则通常将参数声明为const还是不声明为常量(除非它包含引用成员-内置类型不是问题)。如果参数是引用或指针,通常最好保护引用/指向的内存,而不是指针本身(我认为您不能使引用本身为const,这并不重要,因为您不能更改引用者) 。保护作为const的所有内容似乎是一个好主意。如果参数只是POD(包括内置类型),并且没有沿途进一步更改的机会(例如,在您的示例中为bool参数),则可以忽略它而不必担心会犯错误。

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


0

我不会将const放在这样的参数上-每个人都已经知道布尔值(而不是boolean&)是常量,因此添加它会使人们觉得“等等,怎么办?” 甚至您是通过引用传递参数。


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

0

使用const时要记住的是,从一开始就使const变得容易得多,而不是以后尝试将它们放入。

如果您希望某些内容保持不变,请使用const-它是一个附加的提示,用于描述函数的功能以及期望的功能。我已经看到许多C API可以使用其中的一些API,尤其是那些接受C字符串的API!

与标题相比,我更倾向于在cpp文件中省略const关键字,但是由于我倾向于剪切+粘贴它们,因此将它们保留在两个位置。我不知道为什么编译器允许这样做,我想这是一个编译器。最佳实践肯定是将const关键字放入两个文件中。


我一点都不明白。 为什么您倾向于在cpp文件(函数定义)中忽略它?那就是它真正意味着什么并且可以捕获错误的地方。 您为什么认为最好在两个地方都使用const?在头文件(函数声明)中,它没有任何意义,并且会使API混乱。使decl和defn看起来完全相同可能会有一些小的价值,但是在我看来,与使API混乱相比,这确实是一个很小的好处。
唐·哈奇

@DonHatch 8年后,哇。无论如何,正如OP所说:“得知您可以从函数声明中的参数中省略const但可以将其包含在函数定义中,我也感到很惊讶”。
gbjbaanb

0

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

使用“ const”的原因是,如果要通过引用传递更大的内容(例如,具有很多成员的结构),则在这种情况下,它可以确保函数无法对其进行修改。或者更确切地说,如果您尝试以常规方式修改它,编译器将抱怨。这样可以防止意外修改。


0

仅当参数通过引用(即引用或指针)传递时,Const参数才有用。当编译器看到const参数时,请确保未在函数体内修改该参数中使用的变量。为什么有人要将按值参数设置为常量?:-)


因为许多的原因。做出一个按值参数const很清楚地说:“我不需要修改它,因此我在声明。如果以后尝试修改它,请给我一个编译时错误,以便我可以修正错误或取消标记为const。因此,这既涉及代码卫生又涉及安全性。对于添加到实现文件中所需要的全部工作,这应该是人们作为纯粹的反射IMO所做的事情。
underscore_d

0

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


0

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

所以我会避免使用额外的const,因为

  • 他们是多余的
  • 他们弄乱了文字
  • 在可能有用或有效的情况下,它们使我无法更改传递的值。

-1

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

在我看来,我们仍然过于局限于C风格的思维方式。在OOP范式中,我们玩的是对象而不是类型。const对象在概念上可能与非const对象不同,特别是在逻辑const的意义上(与逐位const相反)。因此,即使在POD情况下,函数参数的const正确性(也许)过于谨慎,在对象情况下也并非如此。如果函数与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)引用在这里更合适,并且会给您相同的行为。好吧,对。只是给出与我在其他地方看到的不同的图片...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.