我了解指针和引用的语法和一般语义,但是我应该如何决定什么时候在API中使用引用或指针比较合适?
当然,有些情况需要其中一个(操作符++需要引用参数),但一般来说,我发现我更喜欢使用指针(和const指针),因为语法很清楚,变量是破坏性传递的。
例如,在以下代码中:
void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
int a = 0;
add_one(a); // Not clear that a may be modified
add_one(&a); // 'a' is clearly being passed destructively
}
使用指针,它总是(更)明显的发生了什么,所以对于api和类似的地方,清晰度是一个大问题,指针不是比引用更合适吗?这是否意味着引用应该只在必要时使用(例如操作符++)?其中一种是否存在性能问题?
编辑(过时的):
除了允许NULL值和处理原始数组之外,选择似乎还取决于个人喜好。我接受下面的答案,引用谷歌的c++风格指南,因为他们提出的观点是“引用可能会令人困惑,因为它们有值语法,但有指针语义。”
由于需要额外的工作来清除不应该为NULL的指针参数(例如,add_one(0)将调用指针版本并在运行时中断),从可维护性的角度来看,在必须存在对象的地方使用引用是有意义的,尽管失去语法的清晰性是一种遗憾。
就像其他人已经回答的那样:总是使用引用,除非变量为NULL/nullptr是一个真正有效的状态。
约翰·卡马克在这个问题上的观点也类似:
空指针是C/ c++中最大的问题,至少在我们的代码中是这样。将一个值同时用作标志和地址会导致大量致命问题。在任何可能的情况下,c++引用应该比指针更受欢迎;虽然引用“实际上”只是一个指针,但它有一个非null的隐含契约。当指针转换为引用时执行NULL检查,然后可以忽略此问题。
http://www.altdevblogaday.com/2011/12/24/static-code-analysis/
编辑2012-03-13
用户Bret Kuhns正确地评论道:
c++ 11标准已经完成。我认为是时候在这个线程中提到,大多数代码应该可以很好地使用引用、shared_ptr和unique_ptr的组合。
确实如此,但是问题仍然存在,即使用智能指针替换原始指针也是如此。
例如,std::unique_ptr和std::shared_ptr都可以通过它们的默认构造函数构造为“空”指针:
http://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr
http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
... 这意味着在不验证它们是否为空的情况下使用它们会有崩溃的风险,这正是J. Carmack讨论的全部内容。
然后,我们有一个有趣的问题:“如何将智能指针作为函数参数传递?”
Jon对这个问题的回答是c++——将引用传递给boost::shared_ptr,下面的注释表明,即使这样,通过复制或引用传递智能指针也不像人们想的那样明确(默认情况下我倾向于使用“通过引用”,但我可能错了)。
免责声明:除了引用不能为NULL或“反弹”(意味着它们不能改变它们是别名的对象)这一事实之外,它真的归结为一个品味问题,所以我不会说“这更好”。
也就是说,我不同意你在文章中最后的说法,因为我不认为代码在引用中失去了清晰度。在你的例子中,
add_one(&a);
可能会更清楚
add_one(a);
因为你知道a的值很可能会改变。另一方面,函数的签名
void add_one(int* const n);
也不太清楚n是单个整数还是数组?有时您只能访问(文档记录不佳的)头文件和签名
foo(int* const a, int b);
乍一看不容易理解。
在我看来,当不需要(重新)分配或重新绑定(在前面解释的意义上)时,引用和指针一样好。此外,如果开发人员只对数组使用指针,那么函数签名就不会那么模糊。更不用说使用引用的操作符语法可读性更强了。
尽可能使用引用,必须使用指针。
避免指针,直到你不能。
原因是指针使事情更难跟踪/阅读,比任何其他结构更不安全,更危险的操作。
所以经验法则是只有在没有其他选择的情况下才使用指针。
例如,当函数在某些情况下可以返回nullptr时,返回指向对象的指针是一个有效的选项,并且假设它将会返回。也就是说,更好的选择是使用类似于std::optional(需要c++ 17;在此之前,有boost::optional)。
另一个例子是为特定的内存操作使用指向原始内存的指针。这应该隐藏并本地化在代码的非常狭窄的部分,以帮助限制整个代码库的危险部分。
在你的例子中,使用指针作为参数是没有意义的,因为:
如果你提供nullptr作为参数,你将进入未定义行为领域;
引用属性版本不允许(没有容易发现的技巧)1的问题。
对于用户来说,引用属性版本更容易理解:您必须提供一个有效的对象,而不是可以为空的东西。
如果函数的行为必须在有或没有给定对象的情况下工作,那么使用指针作为属性表明您可以将nullptr作为参数传递,这对函数来说是好的。这是用户和实现之间的一种契约。