制作一个基本(非指针)参数const是否有意义?

时间:2010-03-16 04:34:17

标签: c++ const const-correctness

我最近与另一位C ++开发人员就const的以下用法进行了交流:

void Foo(const int bar);

他认为以这种方式使用const是一种很好的做法。

我认为它对函数的调用者没有任何作用(因为参数的副本将被传递,对于覆盖没有额外的安全保证)。此外,这样做可以防止Foo的实现者修改其参数的私有副本。因此,它既要求并广告实施细节。

不是世界末日,但肯定不会被推荐为良好做法

我很好奇别人对这个问题的看法。

编辑:

好吧,我没有意识到参数的常数没有考虑到函数的签名。因此,可以在实现(.cpp)中将参数标记为const,而不是在标题(.h)中标记 - 并且编译器就可以了。既然如此,我想政策应该与制作局部变量const相同。

有人可能会认为在标题和源文件中使用不同的查看签名会使其他人感到困惑(因为它会使我感到困惑)。虽然我尝试按照Principle of Least Astonishment跟随我写的任何内容,但我认为期望开发人员认为这是合法且有用的是合理的。

5 个答案:

答案 0 :(得分:26)

还记得if(NULL == p)模式吗?

有很多人会说“你必须写这样的代码”:

if(NULL == myPointer) { /* etc. */ }

而不是

if(myPointer == NULL) { /* etc. */ }

理由是第一个版本将保护编码器免受代码拼写错误的影响,例如将“==”替换为“=”(因为禁止将值赋给常量值)。

然后可以将以下内容视为此有限if(NULL == p)模式的扩展:

为什么常量参数对编码器有用

无论类型如何,“const”都是一个限定符,我在编译器中说“我不希望值发生变化,所以如果我说谎,请给我发送编译错误消息

例如,这种代码将显示编译器何时可以帮助我:

void bar_const(const int & param) ;
void bar_non_const(int & param) ;

void foo(const int param)
{
   const int value = getValue() ;

   if(param == 25) { /* Etc. */ } // Ok
   if(value == 25) { /* Etc. */ } // Ok

   if(param = 25) { /* Etc. */ } // COMPILE ERROR
   if(value = 25) { /* Etc. */ } // COMPILE ERROR

   bar_const(param) ;  // Ok
   bar_const(value) ;  // Ok

   bar_non_const(param) ;  // COMPILE ERROR
   bar_non_const(value) ;  // COMPILE ERROR

   // Here, I expect to continue to use "param" and "value" with
   // their original values, so having some random code or error
   // change it would be a runtime error...
}

在那些可能由代码错误或函数调用中的错误发生的情况下,编译器会捕获它,这是好东西

为什么它对用户不重要

碰巧:

void foo(const int param) ;

void foo(int param) ;

有相同的签名。

这是一件好事,因为,如果函数实现者决定一个参数在函数内被认为是const,那么用户不应该,也不需要知道它。

这解释了为什么我的函数声明给用户省略了const:

void bar(int param, const char * p) ;

尽可能保持声明清晰,而我的函数定义尽可能地添加声明:

void bar(const int param, const char * const p)
{
   // etc.
}

使我的代码尽可能健壮。

为什么在现实世界中,可以打破

但是,我被我的模式所困扰。

在一些将保持匿名的破坏的编译器(其名称以“ Sol ”开头并以“出现CC ”结尾),上述两个签名可被视为不同(取决于上下文),因此,运行时链接可能失败。

由于项目也是在Unix平台(Linux和Solaris)上编译的,在这些平台上,未定义的符号在执行时仍然被解析,这在执行过程中引发了运行时错误。

所以,因为我必须支持所说的编译器,所以我用污染的原型结束污染我的标题。

但是我仍然认为这种在函数定义中添加const的模式很好。

注意:Sun Microsystems甚至还有一些球可以隐藏他们破碎的破损,但是“这是邪恶的模式,所以你不应该使用它”声明。见http://docs.oracle.com/cd/E19059-01/stud.9/817-6698/Ch1.Intro.html#71468

最后一个注释

必须注意的是,Bjarne Stroustrup似乎反对将void foo(int)void foo(const int)相同的原型视为:

  

尽管如此,并非所有接受的功能都是我认为的改进。例如,void f(T)和void f(const T)的规则表示相同的函数(由Tom提出)   Pl出于C兼容性原因)[有]可疑的区别是被投票到C ++“超过我的尸体”。

来源:Bjarne Stroustrup
在现实世界中为现实世界发展语言:C ++ 1991-2006 5。语言特色:1991-1998 ,p21。
http://www.stroustrup.com/hopl-almost-final.pdf

考虑到Herb Sutter提供了相反的观点,这很有趣:

  

指南:在函数声明中避免使用const pass-by-value参数。如果不修改参数const仍然在同一函数的定义中。

来源:Herb Sutter
例外C ++ 第43项:正确性,第177-178页。

答案 1 :(得分:7)

这已经多次讨论过,大多数人最终不得不同意不同意。就个人而言,我同意这是毫无意义的,并且标准隐含地同意 - 顶级const(或volatile)限定符不构成函数签名的一部分。在我看来,想要使用这样的顶级限定符表明(强烈)该人可能会为将界面与实现分离而口头上说服务,但真的 p>

另一个小细节:它确实适用于引用以及指针......

答案 2 :(得分:3)

它使编译器可以完成捕获bug的部分工作。如果您不应该修改它,请将其设为const,如果您忘记了,编译器会对您大喊大叫。

答案 3 :(得分:3)

如果bar被标记为const,那么阅读代码的人知道传入的内容,始终知道完全 bar包含的内容。没有必要事先查看任何代码,以确定在此过程中的任何一点是否更改了条形图。这使得代码的推理变得更简单,从而减少了错误蔓延的机会。

我自己投了“好习惯”。当然,我现在也很擅长转换为函数式语言......


解决以下评论,请考虑以下源文件:

// test.c++

bool testSomething()
{
    return true;
}

int test1(int a)
{
    if (testSomething())
    {
        a += 5;
    }
    return a;
}

int test2(const int a)
{
    if (testSomething())
    {
        a += 5;
    }
    return a;
}

test1中,我无法知道返回的值是什么,如果不读取函数的(可能相当大的和/或错综复杂的)主体而不追踪函数testSomething的(可能是遥远的,相当大的,错综复杂的和/或源不可用的)主体。此外,a的改变可能是可怕的拼写错误的结果。

test2中的同一个拼写错误导致编译时:

$ g++ test.c++
test.c++: In function ‘int test2(int)’:
test.c++:21: error: assignment of read-only parameter ‘a’

如果是拼写错误,它就会被我抓住。如果它不是拼写错误,以下是更好的编码选择,IMO:

int test2(const int a)
{
    int b = a;
    if (testSomething())
    {
        b += 5;
    }
    return b;
}

即使是半生不熟的优化器也会生成与test1案例中相同的代码,但您要发出必须支付关注和注意力的信息。

编写可读性代码涉及的不仅仅是选择时髦的名字。

答案 4 :(得分:3)

我倾向于成为一个const恶魔,所以我个人喜欢它。通常,向代码的读者指出传入的变量不会被修改是有用的;就像我尝试将我在函数体中创建的每个其他变量标记为const一样,如果它没有被修改的话。

我也倾向于保持功能签名匹配,即使它没有多大意义。部分是因为它没有造成任何伤害,部分是因为如果签名不同,Doxygen曾经有点混淆。