何时在函数参数中使用const和const引用?

30

在编写一个C++函数,必须传递参数时,据我所知,如果可以保证对象不会被更改,则应始终使用const,或者如果指针不会被更改,则应使用const指针。

还有哪些情况下建议使用这种做法?

什么时候会使用常量引用,相对于直接通过指针传递它的优点是什么?

那么这个void MyObject::Somefunc(const std::string& mystring)中的const string有什么意义,因为字符串实际上已经是一个不可变对象了?


1
可能是如何在C++中将对象传递给函数?的重复问题。 - sbi
2
原因1:字符串不是不可变的。原因2:能够传递字符串字面值、C-字符串或右值。如果你采用普通引用,就不会应用任何转换,也无法将临时对象作为参数传递。 - Armen Tsirunyan
4个回答

48

很不幸,询问是否添加const是一个错误的问题。

将非const引用与传递非const指针进行比较

void modifies(T &param);
void modifies(T *param);

这个问题主要涉及样式:你想让调用看起来像call(obj)还是call(&obj)?然而,在两个点上,差异很重要。如果要传递null,则必须使用指针。如果要重载运算符,则不能使用指针。

将const引用与按值传递进行比较

void doesnt_modify(T const &param);
void doesnt_modify(T param);

这是一个有趣的案例。经验法则是,“易于复制”的类型通过值传递——这些通常是小型类型(但不总是)——而其他类型通过const引用传递。然而,如果您需要在函数内部进行复制,则应该通过值传递(应该传递值)。(是的,这暴露了一些实现细节。这就是C++。)
比较const指针和非修改加法重载
void optional(T const *param=0);
// vs
void optional();
void optional(T const &param); // or optional(T param)

这与上述不修改的情况有关,只是传递参数是可选的。在所有三种情况中,这里的差异最小,因此选择最方便的方式。当然,非const指针的默认值由您决定。

按值const是实现细节

void f(T);
void f(T const);

这些声明实际上是完全相同的函数! 当按值传递时,const只是一个实现细节。 试一下:

void f(int);
void f(int const) {/*implements above function, not an overload*/}

typedef void C(int const);
typedef void NC(int);
NC *nc = &f;  // nc is a function pointer
C *c = nc;  // C and NC are identical types

如果你想传递NULL并仍然使用引用,请查看“Null Object Pattern”(空对象模式)。 - Björn Pollex
void test(int i)不等同于void test(int const i),因为后者不允许修改i的值。 - Duke

7
一般规则是,尽可能使用const,只有在必要时才省略它。使用const可以使编译器进行优化,并帮助同事理解代码的预期用途(同时编译器也会捕获可能的误用)。
至于你的例子,在C++中字符串不是不可变的。如果将一个非const引用传递给函数,则函数可能会修改它。C++没有内置的不可变性概念,您只能使用封装和const来模拟它(尽管这永远不会是绝对可靠的)。
经过思考@Eamons的评论并阅读一些资料,我同意优化不是使用const的主要原因。主要原因是要编写正确的代码。

3
我认为编译器实际上无法根据const注释来优化任何内容 - const并不意味着不可变性,仅仅表示在此范围内没有更改的意图。 - Eamon Nerbonne
+1 提醒字符串不是不可变的,这非常重要! - Eamon Nerbonne
@Eamon:阅读此文了解有关从“const”进行优化的一些利弊。链接 - Björn Pollex

3
这些问题基于一些错误的假设,所以并不真正有意义。
std::string 并没有模拟不可变的字符串值。它模拟可变值。
不存在所谓的“const引用”。只有对const对象的引用。这种区别微妙但很重要。
函数参数的顶层const只对函数实现有意义,而不是对纯声明有意义(在这种情况下编译器会忽略它)。它不会告诉调用者任何信息。它只是对实现的限制。例如,int const作为函数的纯声明中的参数类型几乎没有意义。然而,std::string const& 中的const并不是顶层的。
通过引用传递const避免了数据的低效复制。通常,对于将数据传递到函数中的参数,您可以通过值传递小项(例如int),并通过引用传递较大的项以进行const修饰。在机器代码中,对const的引用可能被优化掉,也可能被实现为指针。例如,在32位Windows中,int是4个字节,指针也是4个字节。因此,参数类型int const&不会减少数据复制,但是可能会使用简单的编译器引入额外的间接引用,这意味着轻微的效率低下--因此需要区分大小。
祝好!

1
人们经常使用“常量引用”的术语,意思是“指向常量对象的引用”...我会说前者 - 鉴于它没有其他可能的含义或解释 - 是讨论后者的一种非正式方式,而不是存在微小区别的两个事物;-)。 - Tony Delroy
1
你可以将非 const 对象绑定到一个 const 引用上。这里的 const 特性是引用本身的特性,而不是对象的特性。 - Björn Pollex
2
@Tony:这里准确术语的问题非常真实。 "const指针"与"指向const的指针"非常不同(即使一些Microsoft程序员不理解)。而且在MSVC中,“T&const”(“const引用”)会导致编译器惊叹“时代错误:忽略引用上的限定符”,因此虽然对某些人来说这是不可想象的,但在某个时候,这个编译器显然接受了这种方式,因此我认为区分它们非常重要。干杯! - Cheers and hth. - Alf
1
@Alf:假设您有一个函数,它将引用传递给一个const对象作为参数。现在,您使用非“const”对象作为参数调用该函数。这会使对象成为“const”吗?不是的。引用使它在函数中显示为const,但对象本身并不是const。我不是C++标准和定义方面的专家,我只是觉得您的术语有点误导。 - Björn Pollex
1
@Alf:非常正确,但最终我要直截了当地说,我认为你在这个问题上投射了自己的观点,而不是代表C++专家社区的共识或最佳实践 - 更多的是你希望他们如何使用这个术语而不是他们实际使用的方式 - 但是如果你告诉我这些印象是错误的,那么我会接受... 我并不会购买每一本新的C++书籍,也不会参加现在的comp.lang.c++.moderated等会议。 - Tony Delroy
显示剩余10条评论

1
const引用相对于const指针的主要优势在于:明确参数是必需的,且不能为NULL。反之,如果我看到一个const指针,我会立即假设它不是引用的原因是参数可能为空。

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