你与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)
这有什么理由吗?这对我来说似乎有点不寻常。
答案 0 :(得分:381)
“当参数按值传递时,const是没有意义的,因为你不会修改调用者的对象。”
错误。
这是关于自我记录您的代码和您的假设。
如果您的代码中有很多人正在使用它并且您的功能非常重要,那么您应该将“const”标记为任何内容以及您可以使用的所有内容。在编写工业强度代码时,你应该总是假设你的同事是精神病患者试图以任何方式帮助你(特别是因为它通常是你自己的未来)。
此外,正如前面提到的那样,可能可以帮助编译器稍微优化一下(虽然这是一个很长的镜头)。
答案 1 :(得分:166)
原因是参数的const仅在函数中本地应用,因为它正在处理数据的副本。这意味着函数签名实际上是相同的。这样做可能很糟糕。
我个人倾向于不使用const,除了参考和指针参数。对于复制的对象,它并不重要,尽管它可以更安全,因为它在函数内表示意图。这真是一个判断电话。我确实倾向于使用const_iterator但是在循环某些东西时我并不打算修改它,所以我猜他自己的每一个,只要对参考类型的const正确性进行严格维护。
答案 2 :(得分:134)
有时候(经常!)我必须解开其他人的C ++代码。而且我们都知道其他人的 C ++代码几乎是按照定义完全混乱:)所以我做的第一件事就是将解析本地数据流放在每个变量定义中 const 直到编译器开始吠叫。这也意味着const限定值参数,因为它们只是由调用者初始化的奇特局部变量。
啊,我希望变量默认为 const ,非const变量需要可变:)
答案 3 :(得分:75)
以下两行在功能上是等效的:
int foo (int a);
int foo (const int a);
显然,如果a
的主体定义为foo
,则无法修改const
,但与外界没有区别。
int foo (const BigStruct &a);
int foo (const BigStruct *a);
真正派上用场的是参考或指针参数:
{{1}}
这就是foo可以采用一个大参数,也许是一个大小为千兆字节的数据结构,而不是复制它。此外,它告诉调用者,“Foo不会*改变该参数的内容。”传递const引用也允许编译器做出某些性能决定。
*:除非它抛弃了常数,但那是另一个帖子。
答案 4 :(得分:65)
从API角度来看,额外多余的const是不好的:
在您的代码中为值传递的内部类型参数添加额外的多余const 使您的API混乱,同时对调用者或API用户没有任何有意义的承诺(它只会妨碍实现)。
API中太多'const'在不需要时就像“哭狼”,最终人们会开始忽略'const',因为它遍布整个地方并且在大多数时候都没有意义。
API中额外consts的“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
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实际上都是通过强制他在想要更改变量或通过非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,一个恼人的唠叨,一个浅薄而毫无意义的承诺,一个不必要的障碍,偶尔会导致非常危险的错误。
答案 5 :(得分:35)
const应该是C ++中的默认值。 像这样:
int i = 5 ; // i is a constant
var int i = 5 ; // i is a real variable
答案 6 :(得分:25)
当我用C ++编写代码时,我尽可能地把所有东西都拿走了。使用const是帮助编译器帮助您的好方法。例如,修改方法返回值可以避免输错,例如:
foo() = 42
当你的意思是:
foo() == 42
如果foo()被定义为返回非const引用:
int& foo() { /* ... */ }
编译器很乐意让你为函数调用返回的匿名临时值赋值。使它成为const:
const int& foo() { /* ... */ }
消除这种可能性。
答案 7 :(得分:14)
答案 8 :(得分:9)
我说const你的值参数。
考虑这个错误的功能:
bool isZero(int number)
{
if (number = 0) // whoops, should be number == 0
return true;
else
return false;
}
如果number参数是const,编译器会停止并警告我们这个bug。
答案 9 :(得分:7)
我在函数参数上使用const,这些参数只是[in]数据的引用(或指针),不会被函数修改。这意味着,当使用引用的目的是避免复制数据而不允许更改传递的参数时。
在示例中将const放在boolean b参数上只会对实现施加约束,并且不会为类的接口做出贡献(尽管通常建议不更改参数)。
的功能签名
void foo(int a);
和
void foo(const int a);
是相同的,这解释了你的.c和.h
阿萨夫
答案 10 :(得分:7)
->*
或.*
运算符,则必须使用此功能。它会阻止您编写类似
的内容void foo(Bar *p) { if (++p->*member > 0) { ... } }
我现在几乎做了,而且可能不会按你的意愿行事。
我打算说的是
void foo(Bar *p) { if (++(p->*member) > 0) { ... } }
如果我在const
和Bar *
之间添加p
,编译器会告诉我。
答案 11 :(得分:6)
The answer by @Adisak是最好的答案。请注意,此答案在某种程度上是最好的,因为它除了使用声音和音质出色之外,还具有真实代码示例的备份最完善 深思熟虑的逻辑。
const
没有好处。它所做的就是:
const
可能会阻止这样做。const
会不必要地使到处都是const
的代码变得混乱,从而将注意力从拥有安全代码真正必要的const
转移开。const
至关重要,需要使用 ,因为它可以防止在函数外部上进行持续更改而产生不希望有的副作用,因此,当parm仅是输入而不是输入时,每个指针或引用必须使用const
输出。在通过引用或指针传递的参数上仅使用const
的另一个好处是,使真正显而易见哪些参数是指针或引用。坚持并说“当心!在它旁边带有const
的任何参数都是引用或指针!”是另一回事。(来自“ Google C++ Style Guide”)
对于通过值传递的函数参数,const对调用方没有影响,因此不建议在函数声明中使用。参见TotW #109。
既不鼓励也不鼓励在局部变量上使用const。
来源:《 Google C ++样式指南: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>
clang-tidy
为此提供了一些选择: A。还值得注意的是,Clang的短毛绒clang-tidy
具有选项readability-avoid-const-params-in-decls
,described here,以支持使用<{ {1}}用于按值传递功能参数:
检查函数声明是否具有顶级const的参数。
声明中的const值不会影响函数的签名,因此不应将其放在此处。
示例:
const
下面是另外两个示例,我为了完整性和清晰性而添加了自己:
void f(const string); // Bad: const is top level.
void f(const string&); // Good: const is not top level.
B。它还具有以下选项: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]
-https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html
我只需将其复制并粘贴到我的样式指南中即可:
[复制/粘贴开始]
readability-const-return-type
。这样,当预期更改由引用或指针传递的变量时,它会变得很明显,因为它将缺少const
。在这种使用情况下,const
可以防止功能外的意外副作用。const
,因为const
对调用方没有影响:即使变量在函数中已更改功能外不会有任何副作用。请参阅以下资源,以获取更多依据和见解:
const
[ie:const
对参数由值传递的] const
)。它是无意义的,编译器会忽略它,它是视觉噪声,并且可能误导读者”({{3 }},并增加了重点)。
const
限定符是放在函数定义中的限定符,而不是在函数的前向声明中的限定符,例如在头文件中的函数(方法)声明中。 const
[ie:const
用于返回值的变量通过值的] >通过功能。const
取决于实现者 ,因为有时它很有用。const
选项强制执行上述操作:
下面是一些代码示例,用于说明上述clang-tidy
规则:
const
参数示例:
(有些是从https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html借来的)
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]
返回类型示例:
(有些是从here借来的)
const
[复制/粘贴结束]
答案 12 :(得分:5)
答案 13 :(得分:5)
标记值参数'const'绝对是一个主观的东西。
但是我实际上更喜欢将值参数标记为const,就像在您的示例中一样。
void func(const int n, const long l) { /* ... */ }
我的值清楚地表明功能参数值永远不会被函数改变。它们在开头和结尾处具有相同的值。对我来说,这是保持一种非常实用的编程风格的一部分。
对于一个简短的函数,在那里使用'const'可能是浪费时间/空间,因为通常非常明显的是参数不会被函数修改。
但是对于更大的函数,它是一种实现文档形式,它由编译器强制执行。
我可以肯定,如果我用'n'和'l'进行一些计算,我可以重构/移动那个计算,而不用担心得到不同的结果,因为我错过了一个或两个都被改变的地方。
由于它是一个实现细节,因此您不需要在头文件中声明值参数const,就像您不需要使用与实现使用的名称相同的名称来声明函数参数。
答案 14 :(得分:4)
const是没有意义的,因为你不会修改调用者的对象。
const在通过引用传递时应该是首选,除非函数的目的是修改传递的值。
最后,一个不修改当前对象(this)的函数可以,并且可能应该声明为const。一个例子如下:
int SomeClass::GetValue() const {return m_internalValue;}
这是一个不修改应用此调用的对象的承诺。换句话说,您可以致电:
const SomeClass* pSomeClass;
pSomeClass->GetValue();
如果函数不是const,则会导致编译器警告。
答案 15 :(得分:3)
可能这不是一个有效的论据。但是如果我们在函数编译器中增加一个const变量的值会给我们一个错误: “错误:只读参数的增量”。这意味着我们可以使用const关键字来防止意外修改函数内部的变量(我们不应该/只读)。因此,如果我们在编译时意外地完成了它,编译器就会告诉我们。如果您不是唯一一个从事该项目的人,这一点尤为重要。
答案 16 :(得分:2)
我不使用const作为传递值的参数。调用者不关心您是否修改参数,这是一个实现细节。
真正重要的是,如果方法不修改其实例,则将方法标记为const。你去的时候这样做,因为否则你可能会得到很多const_cast&lt;&gt;或者您可能会发现标记方法const需要更改大量代码,因为它调用了其他应该标记为const的方法。
如果我不需要修改它们,我也倾向于标记局部变量const。我相信通过更容易识别“移动部件”,它可以使代码更容易理解。
答案 17 :(得分:2)
我倾向于尽可能使用const。 (或者目标语言的其他适当的关键字。)我这样做纯粹是因为它允许编译器进行额外的优化,否则它将无法进行。由于我不知道这些优化可能是什么,我总是这样做,即使它看起来很愚蠢。
据我所知,编译器可能会很好地看到一个const值参数,并说“嘿,这个函数无论如何都没有修改它,所以我可以通过引用传递并节省一些时钟周期。”我不认为它会做这样的事情,因为它改变了函数签名,但它说明了这一点。也许它会做一些不同的堆栈操作或者其他......重点是,我不知道,但我确实知道比编译器更聪明只会导致我感到羞耻。
C ++有一些额外的包袱,有了const-correctness的概念,所以它变得更加重要。
答案 18 :(得分:2)
在你提到的情况下,它不会影响你的API的调用者,这就是为什么它不常见(并且在标题中没有必要)。它只影响你的函数的实现。
这并不是特别糟糕的事情,但鉴于它不会影响您的API,并且它会增加打字,所以它的好处并不是那么好,所以通常不会这样做。
答案 19 :(得分:2)
关于编译器优化:http://www.gotw.ca/gotw/081.htm
答案 20 :(得分:2)
答案 21 :(得分:1)
如果参数是按值传递的(并且不是引用),通常参数是否声明为const没有太大区别(除非它包含引用成员 - 对于内置类型不是问题)。如果参数是引用或指针,通常最好保护引用/指向内存,而不是指针本身(我认为你不能使引用本身为const,而不是因为你不能更改裁判而重要) 。 保护你能做的所有东西似乎是个好主意。你可以省略它,如果参数只是POD(包括内置类型)而不会出错,并且它们不可能在路上进一步改变(例如在你的例子中bool参数)。
我不知道.h / .cpp文件声明的区别,但它确实有道理。在机器代码级别,没有任何东西是“const”,所以如果你将一个函数(在.h中)声明为非const,那么代码就像你将它声明为const(抛弃优化)一样。但是,它可以帮助您登记编译器,您不会在函数(.ccp)的实现中更改变量的值。当您从允许更改的接口继承时,它可能会派上用场,但您不需要更改为参数来实现所需的功能。
答案 22 :(得分:1)
总结:
std::vector::at(size_type pos)
。标准库的优点对我有好处。答案 23 :(得分:0)
示例中的所有功能都没有用处。默认情况下,C ++是按值传递的,因此该函数会获取这些int和booleans的副本。即使函数确实修改了它们,调用者的副本也不会受到影响。
所以我会避免额外的争论,因为
答案 24 :(得分:0)
当您希望某些内容保持不变时使用const - 它是一个添加的提示,用于描述您的功能和预期功能。我见过很多可以用它们中的一些的C API,尤其是接受c-strings的那些!
我更倾向于省略cpp文件中的const关键字而不是标题,但是因为我倾向于剪切+粘贴它们,所以它们会被保存在两个地方。我不知道编译器为什么允许这样,我猜它是编译器的东西。最佳做法是将const关键字放在两个文件中。
答案 25 :(得分:0)
当参数通过值传递时,如果从调用函数的角度指定const或不指定const,它没有任何区别。将值传递的值声明为const传递基本没有任何意义。
答案 26 :(得分:0)
我不会把const放在这样的参数上 - 每个人都已经知道布尔值(而不是布尔值和布尔值)是常量,所以加入它会让人们想到“等等,什么?”甚至你通过引用传递参数。
答案 27 :(得分:0)
Const参数仅在参数通过引用传递时有用,即引用或指针。当编译器看到const参数时,它确保参数中使用的变量不在函数体内修改。为什么有人想要将by-value参数设为常量? : - )
答案 28 :(得分:0)
实际上没有理由创建一个值参数“const”,因为该函数无论如何都只能修改变量的副本。
使用“const”的原因是你通过引用传递更大的东西(例如一个有很多成员的结构),在这种情况下它确保函数不能修改它;或者更确切地说,如果您尝试以传统方式修改它,编译器会抱怨。它可以防止它被意外修改。
答案 29 :(得分:-1)
我知道问题是&#34;有点&#34;过时了,但随着我的到来,其他人也可能会在将来这样做......我仍然怀疑这位可怜的家伙会在这里列出来阅读我的评论:)
在我看来,我们仍然过于局限于C风格的思维方式。在OOP范例中,我们使用对象而不是类型。 Const对象在概念上可能与非const对象不同,特别是在逻辑const意义上(与bitwise-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)引用在这里更合适并且给你相同的行为。好吧,对。只是给出了我在其他地方看到的不同的画面......
答案 30 :(得分:-1)
作为一名需要使用具有50多个公开函数的C ++程序的VB.NET程序员,以及偶尔使用const限定符的.h文件,很难知道何时使用ByRef或ByVal访问变量。 / p>
当然程序会告诉你在你犯错误的行上产生异常错误,但是你需要猜测2-10个参数中哪一个是错误的。
所以现在我有一个令人厌恶的任务,试图说服开发人员他们应该以一种允许自动创建所有VB.NET函数定义的自动方法的方式真正定义他们的变量(在.h文件中)。然后他们会沾沾自喜地说,“阅读......文件。”
我编写了一个解析.h文件的awk脚本,并创建了所有的Declare Function命令,但没有关于哪些变量是R / O与R / W的指示,它只能完成一半的工作。< / p>
修改强>
在另一位用户的鼓励下,我正在添加以下内容;
这是一个(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文件自我记录,以便使用其他语言的开发人员可以轻松编写工作代码。