C++中基本类型的const修饰符

14

我需要注意使用 const 修饰符是否对原始类型起作用?哪个语法更正确,为什么?

第一个版本:

float Foo::bar(float a, float b)
{
    return (a + b) / 2.0f;
}

第二个版本:

const float Foo::bar(const float a, const float b)
{
    return (a + b) / 2.0f;
}

第三个版本:

float Foo::bar(const float a, const float b)
{
    return (a + b) / 2.0f;
}

我知道原始类型的变量在传递给某个方法时会被复制,但哪种方式更清晰呢?

5个回答

24

我认为第三个版本是最“正确”的。

你告诉编译器参数是const,这是正确的,因为你不修改它们。这可以帮助编译器优化传递参数以及计算。

而返回类型不是const,因为调用者可能想要修改返回值。如果调用者不想修改返回值,那么他可以将其分配给一个const变量。

我也会在函数声明中添加const,因为该函数不会修改对象中的任何内容:

float Foo::bar(const float a, const float b) const

既然该函数不访问对象的任何成员,那么我也会将其声明为static


2
+1 const成员函数(虽然对OP提出的问题有所帮助)。 - wmorrison365
3
“呼叫者可能想要修改返回的值”,虽然他们需要使用const_cast(或C风格的转换)来这样做,因为它是基本类型的rvalue表达式。 因此,即使它是非const的,他们也不能获得非const引用而不经过转换。 不确定这是我们真正需要支持的功能,但更重要的是,如果你的函数返回一个类类型的const对象,那通常是不好的,因为它防止了从中移动或调用其非const成员函数。 - Steve Jessop
另外一个细节是,如果返回类型是 bool,最好将其声明为 const,以避免在条件表达式中出现意外的赋值。虽然可以通过提高编译器警告来避免这种情况,但是,被咬一次后会对此更加小心谨慎。 - Peter Wood
将参数(和作用域变量)设置为const在维护期间避免创建错误也会有所帮助。在这个简单的例子中可能不需要,但是在具有许多类似名称变量的复杂函数中,它可能很有用。 - John Dibling

4
首先,您提供的所有定义在语法上都是正确的。如果它们编译通过,则从语法上讲它们是正确的。
在您的示例代码的特定情况下,方法Foo::bar不会修改参数,因此使用const限定符根本没有任何效果。
然而,您可能希望在所有情况下默认使用const,并仅在允许修改的情况下删除它。因此,在Foo::bar的参数中应用它是一个好主意。我认为这是一个好习惯,尽管我承认我很少使用它,因为它会产生一些噪音,可能会降低可读性。
另一个要考虑的问题是,对于原始类型,或更准确地说,不是指针或不包含指针的类型,修改按值传递的参数(即不按引用传递)将不会有任何副作用:这些类型的参数实际上是已初始化的局部变量(这可能很方便,但也可能令人困惑)。对于指针,指向的数据的任何修改都会泄漏到外部世界。这是在指针和类型的指向部分上同时使用const限定符的另一个很好的理由。
总之,尽可能多地使用const限定符将有助于使代码更少出错,并且还可以帮助编译器优化生成的程序。
然而,对于适合CPU寄存器的值类型描述的这些类型,使用引用不应该产生任何重大变化,
因此,这个方法的所有三个版本应该归结为同一个生成的汇编代码。
在原始返回类型的特定情况下,这并不重要。返回值可以来回转换为const限定符。
其他人也提到了函数本身使用const限定符的兴趣。虽然超出了原始问题的范围,但我也会说,尽可能将函数标记为const是更好的(例如Foo::bar)。

3
“will also help the compiler optimize” -- “will” 有点强烈。我很久以前在https://dev59.com/kEjSa4cB1Zd3GeqPFGg-上提出了一个问题,答案表明,将原始变量加上const限定符有助于gcc3进行优化,但是gcc4已经足够聪明,无需协助。 - Steve Jessop
也许吧。对于原始类型来说,这或许并不是很重要。 - didierc

1

第二个版本和第三个版本没有任何区别。选择最短的一个来输入 :)

第一个版本和第三个版本有一点不同。如果你担心在函数内意外修改 ab,你可能更喜欢第三个版本。


虽然第二个和第三个答案之间有所不同。第二个返回一个“const float”,这意味着它返回的值是不可更改的。而在第三个答案中,你当然可以更改它。 - JasoonS
返回值是原始类型而不是左值,因此返回值上的const没有任何作用。例如,在OP的三个签名中的任何一个中编写Foo::bar(1, 2)++都是非法的。 - Bob Carpenter

1

简短回答:这没关系。

详细回答:由于您通过值传递两个参数并通过值返回参数,因此任何一种方式都可以,但是第一个版本更常见。

如果您通过引用传递(正如其他人建议的那样),则确实很重要,您应该使用const引用。但是,通过引用传递基本类型并不真正为您提供任何优势或有意义(如果它是const引用)。原因是通过按值传递基本类型与通过引用传递基本类型相比不会产生任何开销。


1
我要说的是,对于基元类型,实际上复制它们可能更有效率。当你传递引用时,编译器仍然必须在堆栈上传递字节,然后必须解除引用地址以获取内容。
此外,按值传递可以克服有关所传递内容的内存的任何可能的并发/易变性问题。
这是一个“不要试图在这里进行优化”的情况。
通过const返回是一种风格。我通常不这样做,其他人喜欢这样做,以防万一有人会对返回值进行操作。接下来,您会发现人们通过r-value引用返回它们...
我通常会选择您的第一个选项。另一种选择是按值传递(不需要使用const),并通过const值返回。

接下来你会发现有人通过r-value引用返回它们,这会引起未定义行为,因为你不能像返回普通引用一样返回局部对象的rvalue引用。希望这些人能够很快得到治愈。 - Steve Jessop
问题有改变吗?所有参数都是“按值传递”。 - user2864740

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接