将基本(非指针)参数设置为常量是否有意义?

发布于 2024-08-25 10:32:51 字数 634 浏览 5 评论 0原文

最近我和另一位 C++ 开发人员就 const 的如下使用进行了交流:

void Foo(const int bar);

他觉得这样使用 const 是一个很好的做法。

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

这不是世界末日,但肯定不是值得推荐的良好实践

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

编辑:

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

有人可能会说,在头文件和源文件中具有不同的外观签名会让其他人感到困惑(就像它会让我感到困惑一样)。虽然我尝试在我写的任何内容中遵循最小惊讶原则,但我想这是合理的预期开发人员承认这是合法且有用的。

I recently had an exchange with another C++ developer about the following use of const:

void Foo(const int bar);

He felt that using const in this way was good practice.

I argued that it does nothing for the caller of the function (since a copy of the argument was going to be passed, there is no additional guarantee of safety with regard to overwrite). In addition, doing this prevents the implementer of Foo from modifying their private copy of the argument. So, it both mandates and advertises an implementation detail.

Not the end of the world, but certainly not something to be recommended as good practice.

I'm curious as to what others think on this issue.

Edit:

OK, I didn't realize that const-ness of the arguments didn't factor into the signature of the function. So, it is possible to mark the arguments as const in the implementation (.cpp), and not in the header (.h) - and the compiler is fine with that. That being the case, I guess the policy should be the same for making local variables const.

One could make the argument that having different looking signatures in the header and source file would confuse others (as it would have confused me). While I try to follow the Principle of Least Astonishment with whatever I write, I guess it's reasonable to expect developers to recognize this as legal and useful.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

像极了他 2024-09-01 10:32:51

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

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

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

而不是

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

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

下面可以被认为是这个有限的 if(NULL == p) 模式的扩展:

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

无论什么类型,“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...
}

在这些情况下,可能因代码拼写错误或函数调用中的某些错误而发生,编译器将捕获它,这是一件好事< /i>.

为什么它对用户来说不重要

碰巧:

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-20065。语言特征:1991-1998,第 21 页。
http://www.stroustrup.com/hopl-almost-final.pdf

考虑到赫伯·萨特提出了相反的观点,这很有趣:

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

资料来源:赫伯·萨特
卓越的 C++第 43 项:常量正确性,第 177-178 页。

Remember the if(NULL == p) pattern ?

There are a lot of people who will tell a "you must write code like this":

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

instead of

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

The rationale is that the first version will protect the coder from code typos like replacing "==" with "=" (because it is forbidden to assign a value to a constant value).

The following can then be considered an extension of this limited if(NULL == p) pattern:

Why const-ing params can be useful for the coder

No matter the type, "const" is a qualifier that I add to say to the compiler that "I don't expect the value to change, so send me a compiler error message should I lie".

For example, this kind of code will show when the compiler can help me:

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...
}

In those cases, which can happen either by code typo or some mistake in function call, will be caught by the compiler, which is a good thing.

Why it is not important for the user

It happens that:

void foo(const int param) ;

and:

void foo(int param) ;

have the same signature.

This is a good thing, because, if the function implementer decides a parameter is considered const inside the function, the user should not, and does not need to know it.

This explains why my functions declarations to the users omit the const:

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

to keep the declaration as clear as possible, while my function definition adds it as much as possible:

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

to make my code as robust as possible.

Why in the real world, it could break

I was bitten by my pattern, though.

On some broken compiler that will remain anonymous (whose name starts with "Sol" and ends with "aris CC"), the two signatures above can be considered as different (depending on context), and thus, the runtime link will perhaps fail.

As the project was compiled on a Unix platforms too (Linux and Solaris), on those platforms, undefined symbols were left to be resolved at execution, which provoked a runtime error in the middle of the execution of the process.

So, because I had to support the said compiler, I ended polluting even my headers with consted prototypes.

But I still nevertheless consider this pattern of adding const in the function definition a good one.

Note: Sun Microsystems even had the balls to hide their broken mangling with an "it is evil pattern anyway so you should not use it" declaration. see http://docs.oracle.com/cd/E19059-01/stud.9/817-6698/Ch1.Intro.html#71468

One last note

It must be noted that Bjarne Stroustrup seems to be have been opposed to considering void foo(int) the same prototype as void foo(const int):

Not every feature accepted is in my opinion an improvement, though. For example, [...] the rule that void f(T) and void f(const T) denote the same function (proposed by Tom
Plum for C compatibility reasons) [have] the dubious distinction of having been voted into C++ “over my dead body”.

Source: Bjarne Stroustrup
Evolving a language in and for the real world: C++ 1991-2006, 5. Language Features: 1991-1998, p21.
http://www.stroustrup.com/hopl-almost-final.pdf

This is amusing to consider Herb Sutter offers the opposite viewpoint:

Guideline: Avoid const pass-by-value parameters in function declarations. Still make the parameter const in the same function's definition if it won't be modified.

Source: Herb Sutter
Exceptional C++, Item 43: Const-Correctness, p177-178.

迎风吟唱 2024-09-01 10:32:51

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

另一个小细节:它确实适用于引用,就像指针一样......

This has been discussed many times, and mostly people end up having to agree to disagree. Personally, I agree that it's pointless, and the standard implicitly agrees -- a top-level const (or volatile) qualifier doesn't form part of the function's signature. In my opinion, wanting to use a top-level qualifier like this indicates (strongly) that the person may pay lip-service to separating interface from implementation, but doesn't really understand the distinction.

One other minor detail: it does apply to references just as well as pointers though...

红玫瑰 2024-09-01 10:32:51

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

It makes the compiler do part of the work of catching your bugs. If you shouldn't be modifying it, make it const, and if you forget, the compiler will yell at you.

心碎无痕… 2024-09-01 10:32:51

如果 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 情况相同的代码,但您表示要关心和必须予以注意。

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

If bar is marked const as above, then the person reading the code, knowing what was passed in, knows at all time exactly what bar contains. There's no need to look at any code beforehand to see if bar got changed at any point along the way. This makes reasoning about the code simpler and thus reduces the opportunity for bugs to creep in.

I vote "good practice" myself. Of course I'm also pretty much a convert to functional languages these days so....


Addressing the comment below, consider this source file:

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

In test1 there is no way for me to know what the value being returned will be without reading the (potentially sizable and/or convoluted) body of the function and without tracking down the (potentially distant, sizable, convoluted and/or source-unavailable) body of the function testSomething. Further, the alteration of a may be the result of a horrific typo.

That same typo in test2 results in this at compile-time:

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

If it was a typo, it's been caught for me. If it isn't a typo, the following is a better choice of coding, IMO:

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

Even a half-baked optimizer will generate identical code as in the test1 case, but you're signalling that care and attention will have to be paid.

Writing code for readability involves a whole lot more than just picking snazzy names.

晌融 2024-09-01 10:32:51

我是一个const迷,所以我个人很喜欢它。大多数情况下,向代码读者指出传入的变量不会被修改是有用的;就像我尝试将函数体内创建的所有其他变量标记为 const(如果未修改)一样。

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

I tend to be a bit of a const fiend so I personally like it. Mostly it's useful to point out to the reader of the code that the variable passed in wont be modified; in the same way that I try to mark every other variable that I create within a function body as const if it's not modified.

I also tend to keep the function signatures matching even though there's not much point in it. Partly it's because it doesn't do any harm and partly it's because Doxygen used to get a bit confused if the signatures were different.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文