使用'const'作为函数参数

你用const去多远? 你是否只是在必要时使函数成为const ,或者你是否全力以赴地使用它? 例如,设想一个简单的使用单个布尔参数的增变器:

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

那个const实际上有用吗? 我个人select广泛使用它,包括参数,但在这种情况下,我想知道它是否值得?

我也惊讶地发现,你可以在函数声明中忽略const的参数,但是可以将它包含在函数定义中,例如:

.h文件

 void func(int n, long l); 

.cpp文件

 void func(const int n, const long l) 

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

原因是该参数的const只适用于本地函数内,因为它正在处理数据的副本。 这意味着函数签名实际上是相同的。 尽pipe这样做很可能是不好的风格。

我个人倾向于不使用const,除了引用和指针参数。 对于复制的对象,它并不重要,但它可以更安全,因为它表示函数内的意图。 这真是一个判断力的呼唤。 我确实倾向于使用const_iterator,虽然在循环时我不打算修改它,所以我想每个人都可以,只要const参考types的正确性得到严格维护。

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

错误。

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

如果你的代码有很多人在工作,你的函数是不平凡的,那么你应该标记“const”,你可以做任何事情。 在写行业代码的时候,你应该总是假设你的同事是精神病患者,试图让你得到任何可能的方式(特别是因为它往往是你自己的未来)。

另外,正如前面提到的那样,这可能会帮助编译器优化一些东西(尽pipe这是一个很长的一步)。

有时(太频繁!)我必须解开别人的C ++代码。 我们都知道别人的 C ++代码几乎是按照定义完成的:)所以我做的第一件事情就是在每一个variables定义中都将const放入const ,直到编译器开始吠叫。 这也意味着const值限定的参数,因为它们只是调用者初始化的局部variables。

嗯,我希望variables默认是const ,非常量variables需要mutable

以下两行在function上是等同的:

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

很明显,如果第二种方法被定义的话,你将无法修改foo的内容,但是与外部没有任何区别。

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

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

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

*:除非它消除了常量,但这是另一个post。

额外多余的const从API的angular度来看是不好的:

在代码中添加额外多余的const值以传递值为参数的内部types参数混淆了您的API,但对调用方或API用户没有任何有意义的承诺(这只会妨碍实现)。

在不需要API的时候太多的“const”就像是“ 哭闹的狼 ”,最终人们会开始忽视“const”,因为它到处都是,而且大部分时间都没有意义。

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

所以,让我们尽可能地尝试把常量放进去:

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

考虑上面的代码行。 不仅声明更加混乱,阅读时间越来越长,而且四个“常量”关键字中的三个可以被API用户安全地忽略。 但是,额外使用'const'使第二行可能是危险的!

为什么?

第一个参数char * const buffer的快速误读可能会让你认为它不会修改传入的数据缓冲区中的内存,但是这不是真的! 多余的“const”可能会导致您在快速扫描或误读时API的危险和错误的假设


从代码实现的angular度来看,多余的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是“有前途的”,而不是在下面的第一种方式实现该function。

 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]; } } 

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

这两个都是相同function的完美有效的实现,尽pipe如此,你所做的一切都是不必要地背在背后。

此外,这是一个很容易(和合法规避)的非常浅的承诺。

 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放在定义中。

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

然而,相反是真实的……你可以只在声明中放置一个伪造的const,并在定义中忽略它。 这只会使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实际上是通过迫使他使用另一个本地拷贝或包装函数,当他想要改变variables或通过非const引用传递variables时,使得实现者的代码不太可读。

看看这个例子。 哪个更具可读性? 是否显而易见,第二个函数的额外variables的唯一原因是因为一些APIdevise器扔了一个多余的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应该是C ++中的默认值。 喜欢这个 :

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

当我为C ++编写代码时,我把所有可能的东西都放在了一边。 使用const是帮助编译器帮助你的好方法。 例如,强制你的方法返回值可以让你免于错别字,例如:

 foo() = 42 

当你的意思是:

 foo() == 42 

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

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

编译器会很高兴让你给函数调用返回的匿名临时值赋值。 使其不变:

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

消除这种可能性。

关于comp.lang.c ++的旧文章“Guru of the Week”,在这里有一个很好的讨论。

相应的GOTW文章可以在Herb Sutter的网站上find 。

我使用const作为引用(或指针)的函数参数,它们只是[in]数据,不会被函数修改。 意思是,当使用引用的目的是为了避免复制数据,而不允许改变传递的参数。

在你的例子中,将const放在布尔b参数上只会对实现施加一个约束,并不会影响类的接口(尽pipe通常不build议更改参数)。

的函数签名

 void foo(int a); 

 void foo(const int a); 

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

阿萨夫

我说const你的价值参数。

考虑这个越野车function:

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

如果数字参数是const,编译器会停止并警告我们这个错误。

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

它阻止你写类似的东西

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

我现在差不多做了,可能不会做你想要的。

我想说的是

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

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

啊,一个艰难的。 一方面,一个声明是一个合约,并且按值传递一个const参数是没有意义的。 另一方面,如果你看一下函数的实现,那么如果你声明一个参数常量,就给了编译器更多的优化机会。

因为你不会修改调用者的对象,所以const通过值传递时是没有意义的。

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

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

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

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

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

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

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

不过,我更喜欢标记值参数const,就像你的例子。

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

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

对于一个简短的函数来说,在那里使用const可能会浪费时间/空间,因为通常这个函数并没有修改参数。

但是对于一个更大的函数,它是一种实现文档的forms,并且由编译器执行。

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

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

在你提到的情况下,它不会影响你的API的调用者,这就是为什么它不常用(在头文件中是不必要的)。 它只影响你的function的执行。

这不是一件特别糟糕的事情,但是它的好处并不是很大,因为它不会影响你的API,并且增加了input,所以通常不会这样做。

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

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

如果我不需要修改它,我也倾向于标记局部variablesconst。 我相信通过更易于识别“移动部分”,使得代码更容易理解。

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

据我所知,编译器可能会很好地看到一个const值参数,并说:“嘿,这个函数不会修改它,所以我可以通过引用,并保存一些时钟周期。 我不认为它会做这样的事情,因为它改变了function签名,但它是重点。 也许它做了一些不同的堆栈操作或什么…重点是,我不知道,但我知道试图比编译器更聪明只会导致我被羞辱。

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

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

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

Const参数只有在parameter passing时才有用,例如引用或指针。 当编译器看到一个const参数时,它确保参数中使用的variables在函数体内不被修改。 为什么会有人想要一个按值参数保持不变? 🙂

如果参数是通过值(而不是参考)传递的,那么参数是否被声明为const(除非它包含引用成员 – 对于内置types不是问题),通常没有太大区别。 如果参数是一个引用或指针,通常最好保护被引用/指向的内存,而不是指针本身(我认为你不能把引用本身设为const,不是那么重要,因为你不能改变裁判) 。 保护你所能做的一切似乎是个好主意。 如果参数只是POD(包括内置types),并且没有机会在道路上进一步改变(例如在您的示例中为bool参数),则可以忽略它。

我不知道.h / .cpp文件声明的区别,但它确实是有道理的。 在机器代码级别,没有什么是“const”,所以如果你声明一个函数(在.h中)作为非const,那么代码就像你把它声明为const(抛开优化)一样。 但是,它可以帮助您使编译器不会在函数(.ccp)的实现中更改variables的值。 当你从一个允许改变的接口inheritance的情况下,它可能会派上用场,但是你不需要改变参数来实现所需的function。

在编译器优化上: http : //www.gotw.ca/gotw/081.htm

总结:

  • “通常情况下,传递价值是毫无用处和误导的。” 从GOTW006
  • 但是,您可以像在variables中一样将它们添加到.cpp中。
  • 请注意,标准库不使用const。 例如std::vector::at(size_type pos) 。 标准库的好处对我有好处。

我不会把const放在这样的参数上 – 每个人都知道一个布尔值(而不是布尔值)是不变的,所以join它会让人们想“等等,什么? 甚至你通过引用传递参数。

用const记住的事情是,从一开始就把事情变得更加容易,而不是稍后再尝试。

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

我更倾向于忽略cpp文件中的const关键字,而不是标题,但是由于我倾向于剪切+粘贴它们,所以它们会保存在两个地方。 我不知道为什么编译器允许,我猜它是一个编译器的东西。 最好的做法是把你的const关键字放在两个文件中。

真的没有理由做一个值参数“const”,因为函数只能修改variables的一个副本。

The reason to use "const" is if you're passing something bigger (eg a struct with lots of members) by reference, in which case it ensures that the function can't modify it; or rather, the compiler will complain if you try to modify it in the conventional way. It prevents it from being accidentally modified.

As parameters are being passed by value,it doesnt make any difference if you specify const or not from the calling function's perspective.It basically does not make any sense to declare pass by value parameters as const.

All the consts in your examples have no purpose. C++ is pass-by-value by default, so the function gets copies of those ints and booleans. Even if the function does modify them, the caller's copy is not affected.

So I'd avoid extra consts because

  • They're redudant
  • They clutter up the text
  • They prevent me from changing the passed in value in cases where it might be useful or efficient.

I know the question is "a bit" outdated but as I came accross it somebody else may also do so in future… …still I doubt the poor fellow will list down here to read my comment 🙂

It seems to me that we are still too confined to C-style way of thinking. In the OOP paradigma we play around with objects, not types. Const object may be conceptually different from a non-const object, specifically in the sense of logical-const (in contrast to bitwise-const). Thus even if const correctness of function params is (perhaps) an over-carefulness in case of PODs it is not so in case of objects. If a function works with a const object it should say so. Consider the following code snippet

 #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.: you may argue that (const) reference would be more appropriate here and gives you the same behaviour. Well, right. Just giving a different picture from what I could see elsewhere…

Being a VB.NET programmer that needs to use a C++ program with 50+ exposed functions, and a .h file that sporadically uses the const qualifier, it is difficult to know when to access a variable using ByRef or ByVal.

Of course the program tells you by generating an exception error on the line where you made the mistake, but then you need to guess which of the 2-10 parameters is wrong.

So now I have the distasteful task of trying to convince a developer that they should really define their variables (in the .h file) in a manner that allows an automated method of creating all of the VB.NET function definitions easily. They will then smugly say, "read the … documentation."

I have written an awk script that parses a .h file, and creates all of the Declare Function commands, but without an indicator as to which variables are R/O vs R/W, it only does half the job.

编辑:

At the encouragement of another user I am adding the following;

Here is an example of a (IMO) poorly formed .h entry;

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

The resultant VB from my script;

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

Note the missing "const" on the first parameter. Without it, a program (or another developer) has no Idea the 1st parameter should be passed "ByVal." By adding the "const" it makes the .h file self documenting so that developers using other languages can easily write working code.