16

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

void Foo(const int bar);

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

我认为它对函数的调用者没有任何作用(因为要传递参数的副本,所以在覆盖方面没有额外的安全保证)。此外,这样做可以防止实现者Foo修改参数的私有副本。因此,它既规定又宣传实施细节。

不是世界末日,但肯定不是值得推荐的好做法

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

编辑:

好的,我没有意识到参数的常量性并没有考虑到函数的签名。因此,可以将参数标记为const实现 (.cpp),而不是标头 (.h) - 编译器对此很好。既然如此,我想将局部变量设为 const 的策略应该是相同的。

有人可能会说,在头文件和源文件中具有不同外观的签名会混淆其他人(因为它会让我感到困惑)。虽然我尝试在写任何东西时遵循最小惊讶原则,但我想期望开发人员认为这是合法和有用的是合理的。

4

5 回答 5

30

还记得if(NULL == p)图案吗?

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

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

代替

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

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

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

为什么 const-ing 参数对编码器有用

无论是哪种类型,“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 ”开头并以“ aris CC ”结尾)上,上面的两个签名可以被认为是不同的(取决于上下文),因此,运行时链接可能会失败。

由于该项目也是在 Unix 平台(Linux 和 Solaris)上编译的,因此在这些平台上,未定义的符号在执行时需要解析,这会在进程执行过程中引发运行时错误。

所以,因为我必须支持上述编译器,所以我什至用 consted 原型结束了我的头文件。

但是我仍然认为这种在函数定义中添加 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 Plum 出于 C 兼容性原因提出)[有] 被选入 C++ 的可疑区别“越过我的尸体”。

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

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

指南:避免在函数声明中使用 const 值传递参数。如果不修改,仍将参数 const 放在同一函数的定义中。

资料来源:Herb Sutter
Exceptional C++第 43 条:Const- Correctness,p177-178。

于 2010-03-16T09:50:20.863 回答
7

这已经讨论了很多次,大多数人最终不得不同意不同意。就个人而言,我同意这是毫无意义的,标准也隐含同意——顶级 const (或 volatile )限定符不构成函数签名的一部分。在我看来,想要使用这样的顶级限定符(强烈)表明该人可能会口头上将接口与实现分开,但并不真正理解其中的区别。

另一个小细节:尽管它确实适用于引用和指针......

于 2010-03-16T04:43:50.833 回答
4

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

于 2010-03-16T04:39:17.437 回答
3

如果如上bar标记const,则阅读代码的人知道传入的内容,始终知道确切包含bar的内容。无需事先查看任何代码以查看 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例相同的代码,但您表示必须小心谨慎。

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

于 2010-03-16T04:39:19.853 回答
3

我倾向于有点const像恶魔,所以我个人喜欢它。大多数情况下,向代码的读者指出传入的变量不会被修改是有用的;就像我尝试标记我在函数体内创建的所有其他变量一样,就const好像它没有被修改一样。

即使没有太多意义,我也倾向于保持函数签名匹配。部分是因为它不会造成任何伤害,部分是因为如果签名不同,Doxygen 过去常常会有点困惑。

于 2010-03-16T09:59:54.990 回答