使用'const'对于函数参数

你用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)

这有什么原因吗?这对我来说有点不寻常。

353761 次浏览

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

我个人倾向于不使用const,除了引用和指针参数。对于复制的对象来说,这并不重要,尽管它可以更安全,因为它表明了函数中的意图。这真的是一个主观判断。我确实倾向于使用const_iterator,但当循环某些东西时,我不打算修改它,所以我想每个人都有自己的,只要严格维护引用类型的const正确性。

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

在你的例子中,将const放在布尔b形参上只是对实现施加了约束,对类的接口没有贡献(尽管通常建议不改变形参)。

的函数签名

void foo(int a);

而且

void foo(const int a);

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

Asaf

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

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

我不会把const放在这样的形参上——每个人都知道布尔值(相对于boolean&)是常量,所以添加它会让人们认为“等等,什么?”或者甚至认为你是通过引用传递形参。

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

当你想要某些东西保持不变时,使用const -它是一个附加的提示,描述了你的函数做什么以及期望什么。我见过许多C API可以处理其中的一些,特别是那些接受C -string的API !

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

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

你的例子中所有的const都没有目的。c++默认情况下是值传递的,因此该函数获得这些整型和布尔型的副本。即使函数修改了它们,调用者的副本也不会受到影响。

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

  • 他们redudant
  • 他们弄得乱七八糟 李文本< / > 他们阻止我 更改传入的值

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

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

只要可以,我就用const。参数的Const意味着它们不应该改变它们的值。这在通过引用传递时尤其有价值。Const for function声明该函数不应更改类成员。

我不使用const作为传递值的参数。调用者并不关心你是否修改参数,这是一个实现细节。

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

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

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

如果形参是按值传递的(并且不是引用),通常形参是否声明为const没有太大区别(除非它包含引用成员——对于内置类型不是问题)。如果形参是引用或指针,通常更好的是保护被引用/指向的内存,而不是指针本身(我认为你不能使引用本身为const,这并不重要,因为你不能改变引用)。 用const来保护你所能保护的一切似乎是个好主意。如果参数只是pod(包括内置类型),并且它们没有机会在以后发生变化(例如,在你的例子中是bool参数),你可以省略它而不用担心犯错误

我不知道.h/.cpp文件声明的区别,但它确实有一定的意义。在机器代码级别,没有任何东西是“const”的,所以如果你将一个函数(在.h中)声明为非const,代码就像你将它声明为const一样(优化除外)。但是,它可以帮助你向编译器保证你不会在函数的实现(.ccp)中改变变量的值。当您从允许更改的接口继承,但不需要更改参数来实现所需的功能时,它可能会派上用场。

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

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

c++有一些额外的包袱,有常量正确性的思想,所以它变得更加重要。

下面两行在功能上是等价的:

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

显然,如果用第二种方式定义a,则无法在foo的主体中修改a,但从外部看没有区别。

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

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

这就是说,foo可以接受一个大的参数,也许是一个千兆字节大小的数据结构,而不需要复制它。同时,它也告诉调用者:“Foo不会*改变参数的内容。”传递const引用还允许编译器做出某些性能决定。

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

当参数按值传递时,Const是没有意义的,因为你不会修改调用者的对象。

通过引用传递时应优先使用Const,除非函数的目的是修改传递的值。

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

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

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

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

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

当参数通过值传递时,

const是毫无意义的,因为您将

.

错了。

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

如果你的代码有很多人在处理,而且你的函数不是平凡的,那么你应该将const标记为任何你可以标记的东西。在编写工业级别的代码时,您应该始终假设您的同事都是精神病患者,他们试图以任何方式来对付您(特别是因为将来经常是您自己)。

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

有时候(太频繁了!)我必须理清别人的c++代码。我们都知道别人的 c++代码几乎是完全混乱的定义:)所以我要破译本地数据流的第一件事是把常量放在每个变量定义中,直到编译器开始吠叫。这也意味着const限定值参数,因为它们只是由调用者初始化的花哨局部变量。

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

当我以编写c++为生时,我尽可能地压缩了所有东西。使用const是一种帮助编译器帮助你的好方法。例如,const你的方法返回值可以避免你输入错误,例如:

foo() = 42

你的意思是:

foo() == 42

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

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

编译器很乐意让您为函数调用返回的匿名临时对象赋值。使其为const:

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

消除了这种可能性。

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

然而,我实际上更喜欢将值形参标记为const,就像您的示例中一样。

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

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

对于一个简短的函数,在那里使用'const'可以说是浪费时间/空间,因为通常很明显函数不会修改实参。

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

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

因为它是一个实现细节,所以不需要在头文件中声明值形参const,就像不需要声明与实现使用的同名的函数形参一样。

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

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

int i = 5 ; // i is a constant


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

我说const你的值形参。

考虑这个bug函数:

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

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

在关于comp.lang.c++的旧的“每周大师”文章中有关于这个主题的很好的讨论。主持在这里

相应的GOTW文章可以在Herb Sutter的网站在这里上找到。

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

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

API中过多的“const”在不需要的时候就像&;__abc0 &;,最终人们会开始忽略“const”,因为它到处都是,大多数时候没有任何意义。

“荒谬还原”;如果有更多的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的人声称这是一件好事,因为它允许你只在定义中使用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混乱的眼中钉、一个烦人的唠叨、一个肤浅而无意义的承诺、一个不必要的障碍,并且偶尔会导致非常危险的错误。

如果你使用->*.*操作符,这是必须的。

它会阻止你写出

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

我刚才差点就这么做了,但这可能不是你想要的结果。

我想说的是

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

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

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

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

在我看来,我们仍然太局限于c风格的思维方式。在面向对象编程范式中,我们处理的是对象,而不是类型。Const对象在概念上可能与非Const对象不同,特别是在逻辑- Const的意义上(与bitwise-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;


}

附注:你可能会认为(const)引用在这里更合适,并提供相同的行为。嗯,对的。只是给出了与我在其他地方看到的不同的画面……

做一个VB。NET程序员需要使用具有50多个公开函数的c++程序,以及偶尔使用const限定符的.h文件,很难知道何时使用ByRef或ByVal访问变量。

当然,程序通过在出错的行上生成一个异常错误来告诉您,但是随后您需要猜测2-10个参数中哪一个是错误的。

所以现在我有一个令人讨厌的任务,试图说服开发人员,他们应该真正定义他们的变量(在.h文件中),以一种允许自动创建所有VB的方法。NET函数定义容易。然后他们会自鸣得意地说:“读……文档”。

我写了一个awk脚本,它解析一个.h文件,并创建所有的Declare Function命令,但没有指示哪个变量是R/O vs R/W,它只完成了一半的工作。

编辑:

在另一位用户的鼓励下,我添加了以下内容;

下面是一个(IMO)格式不佳的.h条目的例子;

typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );

从我的脚本的结果VB;

    Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer

注意,第一个参数中缺少“const”。如果没有它,程序(或其他开发人员)就不知道第一个参数应该传递“ByVal”。通过添加“const”,它使.h文件自文档化,以便使用其他语言的开发人员可以轻松地编写工作代码。

总结:

  • 通常情况下,const的值传递是无用的,最多是误导。从GOTW006
  • 但是您可以像处理变量一样在.cpp中添加它们。
  • 注意,标准库不使用const。例如std::vector::at(size_type pos)。对标准库来说足够好的东西对我来说也是好的。

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

根据我的评估,来自@Adisak的答案是最好的答案。注意,这个答案在某种程度上是最好的,因为它也是最好的备份与真实的代码示例除了使用合理和深思熟虑的逻辑。

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

  1. 对于值传递,添加const没有好处。它所做的就是:
    1. 限制实现者每次想要更改源代码中的输入参数时都必须复制(这种更改无论如何都不会产生副作用,因为传入的已经是副本,因为它是按值传递的)。通常情况下,改变一个按值传递的输入参数是用来实现函数的,所以在所有地方添加const会阻碍这一点。
    2. 并且添加const会不必要地使代码到处都是const,从而分散了对拥有安全代码真正必要的const的注意力。
  2. 然而,当处理指针参考文献时,const在需要时是非常重要的,并且使用必须,因为它可以防止持久更改函数带来不希望的副作用,因此当参数只是输入而不是输出时,每个指针或引用必须都使用const。在通过引用或指针传递的参数上使用const 只有还有一个额外的好处,即参数是指针或引用的const0。这是另一件事站出来说“当心!”任何旁边带有const的参数都是引用或指针!”;
  3. 我上面所描述的经常是我工作过的专业软件组织中达成的共识,并且被认为是最佳实践。有时甚至,规则是严格的:永远不要对按值传递的形参使用const,但对仅作为输入的引用或指针传递的形参使用const。

3.谷歌的话说(同意我和最好的答案):

(来自"谷歌c++风格指南")

对于通过value传递的函数形参,const对调用者没有影响,因此不建议在函数声明中使用。看到从# 109

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

来源:“句式用法”;谷歌c++样式指南的https://google.github.io/styleguide/cppguide.html#Use_of_const部分。这是一个非常有价值的部分,所以请阅读整个部分。

注意" tow #109"代表第109周提示:函数声明中有意义的const,也是一个有用的阅读。它对该做什么提供了更多的信息和较少的说明性,并且基于上下文出现了上面引用的之前关于const的谷歌c++样式指南规则,但由于它提供的清晰性,上面引用的const规则被添加到谷歌c++样式指南中。

还要注意的是,尽管我在这里引用了谷歌c++风格指南来捍卫我的立场,但这并不意味着我总是遵循指南或总是建议遵循指南。他们推荐的一些东西非常奇怪,比如他们的__abc0风格命名约定为“常量名称”;然而,当世界上最成功和最具影响力的技术和软件公司之一使用与我和其他像@Adisak这样的人一样的理由来支持我们在这个问题上的观点时,指出这一点仍然是有用和相关的。

4. Clang的linter clang-tidy有一些选项:

A.同样值得注意的是Clang的linter, 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. "谷歌c++ Style Guide"句式的使用;节< / >
    2. < a href = " https://abseil。io/tips/109" rel="noreferrer">"本周提示#109:有意义的const在函数声明" . io/tips/109" rel="noreferrer">"
    3. Adisak的Stack Overflow回答"使用'const'作为函数参数"
  3. 从来没有使用顶层const[即:const on parameters 通过值传递]用于不是定义的声明中的函数参数(注意不要复制/粘贴无意义的const)。它是没有意义的,被编译器忽略了,它是视觉噪音,可能会误导读者。(https://abseil.io/tips/109,强调添加)。
  4. 唯一对编译有影响的const限定符是放在函数定义中的限定符,而不是放在函数的前向声明中的限定符,例如头文件中的函数(方法)声明。
  5. 从来没有使用顶级const[即:变量通过值传递上的const]在值返回上的函数。
  6. 在函数返回的指针或引用上使用const由实现者决定,因为它有时很有用。
  7. TODO:使用以下clang-tidy选项强制执行上述部分:
  8. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
  9. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

下面是一些代码示例来演示上面描述的const规则:

< p > 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]
< p > 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();

(复制/粘贴结束)

关键词:在函数参数中使用const;编码标准;C和c++编码标准;编码规则;最佳实践;代码标准;Const返回值