何时使用引用与指针
我理解指针和引用的语法和一般语义,但是我应该如何决定何时在API中使用引用或指针?
当然,有些情况需要一个或另一个( operator++
需要一个引用参数),但一般来说,我发现我更喜欢使用指针(和指针),因为语法清楚,variables正在被破坏性地传递。
例如在下面的代码中:
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和类似的指针不比引用更适合? 这是否意味着引用只能在必要时使用(如operator++
)? 有没有任何性能问题与其中一个?
编辑(OUTDATED):
除了允许NULL值和处理原始数组外,似乎select归结为个人偏好。 我已经接受了下面的引用Google的C ++风格指南的答案,因为他们提出了“引用可能会令人困惑,因为它们具有值语法,但是指针语义”。
由于需要额外的工作来清理不应该为NULL的指针参数(例如, add_one(0)
将在运行时调用指针版本和中断),所以从可维护性的angular度来看,在对象必须存在的地方使用引用是add_one(0)
丢失句法清晰度是一种遗憾。
无论您身在何处,都可以使用参考。
避免指针,直到你不能。
原因是指针使事情难以遵循/阅读,不安全和更危险的操作比任何其他构造。
所以经验法则是只有在没有其他select时才使用指针。
例如,返回一个指向对象的指针是一个有效的选项,当函数在某些情况下可以返回nullptr并且假定它会。 也就是说,更好的select是使用类似于boost::optional
东西。
另一个例子是使用指向原始内存的指针来进行特定的内存操作。 这应该被隐藏和本地化在代码的非常狭窄的部分,以帮助限制整个代码库的危险部分。
在你的例子中,使用指针作为参数没有意义,因为:
- 如果你提供
nullptr
作为参数,你将进入undefined-behavior-land; - 参考属性版本不允许(不容易find招数)1的问题。
- 引用属性版本更易于理解用户:您必须提供有效的对象,而不是可能为空的对象。
如果函数的行为必须使用或不使用给定的对象,那么使用指针作为属性表明,您可以传递nullptr
作为参数,并且对于函数来说是正确的。 这是用户和实现之间的合同。
性能完全一样,因为引用是作为指针在内部实现的。 因此,您不必担心这一点。
关于何时使用引用和指针没有普遍接受的约定。 在less数情况下,您必须返回或接受引用(例如复制构造函数),但除此之外,您可以随意执行。 我遇到的一个相当常见的约定是在参数必须引用现有对象时使用引用,而在NULL值正确时使用引用。
一些编码约定(如Google的 )规定应该总是使用指针或者const引用,因为引用有一些不清楚的语法:它们有引用行为,但是有值语法。
从C ++ FAQ Lite –
尽可能使用参考,必要时使用指示。
只要不需要“重新设置”,引用通常优先于指针。 这通常意味着引用在类的公共接口中是最有用的。 引用通常出现在对象的皮肤上,指向内部的指针。
上面的例外是函数的参数或返回值需要一个“哨兵”引用 – 一个引用不引用一个对象。 这通常最好通过返回/获取一个指针来完成,并且赋予NULL指针这个特殊的意义(引用必须总是别名对象,而不是一个取消引用的空指针)。
注意:旧的C语言程序员有时不喜欢引用,因为他们提供的引用语义在调用者的代码中并不明确。 然而,经过一些C ++经验之后,人们很快意识到这是一种信息隐藏,这是一种资产而不是责任。 例如,程序员应该用问题的语言来编写代码,而不是使用机器的语言。
我的经验法则是:
- 使用指针传出或input/输出参数。 所以可以看出价值将会被改变。 (您必须使用
&
) - 如果NULL参数是可接受的值,则使用指针。 (如果它是传入参数,请确保它是
const
) - 如果传入参数不能为NULL,并且不是原始types(
const T&
),则使用引用。 - 返回新创build的对象时使用指针或智能指针。
- 使用指针或智能指针作为结构或类成员,而不是引用。
- 使用引用别名(例如
int ¤t = someArray[i]
)
无论你使用哪一个,不要忘记logging你的function和参数的含义,如果它们不明显。
免责声明:除了引用不能为NULL或“反弹”(意思是不能改变对象的别名)这个事实之外,它确实归结为一个味道的问题,所以我不打算说“这个更好”。
也就是说,我不同意你在post中的最后一个陈述,因为我不认为这个代码在参考文献中失去了清晰度。 在你的例子中,
add_one(&a);
可能比这更清晰
add_one(a);
因为你知道最有可能的价值将会改变。 另一方面,虽然,function的签名
void add_one(int* const n);
有点不清楚:n是一个整数还是一个数组? 有时你只能访问(很差的文档)标题和签名
foo(int* const a, int b);
不容易一见解。
如果不需要(重新)分配和重新绑定(在前面解释的意义上),那么引用和指针一样好。 而且,如果开发人员只使用数组的指针,那么函数签名就不那么模糊了。 更不要说操作符语法更容易被引用读取的事实。
像其他人一样已经回答:总是使用引用,除非variables是NULL
/ nullptr
真的是一个有效的状态。
约翰·卡马克关于这个问题的观点是相似的:
NULL指针是C / C ++中最大的问题,至less在我们的代码中。 双重使用单一值作为标志和地址会导致令人难以置信的致命问题。 只要可能,C ++引用应该优先于指针; 而引用是“真正”只是一个指针,它具有非NULL的隐式约定。 当指针变成引用时执行NULL检查,然后你可以忽略这个问题。
http://www.altdevblogaday.com/2011/12/24/static-code-analysis/
编辑2012-03-13
用户Bret Kuhns正确地评论道:
C ++ 11标准已经完成。 我认为现在是时候在这个线程中提到,大多数代码应该完全正确地使用引用,shared_ptr和unique_ptr的组合。
真的够了,但问题仍然存在,即使用智能指针replace原始指针。
例如, 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
…意味着在没有validation它们不是空的情况下使用它们会导致崩溃,这正是J.卡马克所讨论的内容。
然后,我们有一个有趣的问题:“我们如何通过智能指针作为函数参数?”
乔恩的答案 C ++的问题- 传递引用boost :: shared_ptr ,和下面的评论显示,即使这样,传递一个智能指针通过复制或通过引用并不像人们想象的那样清晰(我喜欢自己的“通过引用“默认情况下,但我可能是错的)。
这不是一个味道的问题。 这是一些明确的规则。
如果你想在它声明的范围内引用一个静态声明的variables,那么使用C ++引用,这将是非常安全的。 这同样适用于静态声明的智能指针。 按引用传递参数就是这种用法的一个例子。
如果你想引用比范围更广泛的范围,那么你应该使用一个引用计数智能指针,它是非常安全的。
你可以引用一个集合中的元素来引用句法的方便,但这并不安全; 元素可以随时删除。
要安全地持有对集合元素的引用,您必须使用引用计数智能指针。
任何性能差异将会很小,以至于不能使用不太清楚的方法。
首先,一个引用通常比较const
地方没有提到的情况是const
引用。 对于非简单types,传递一个const reference
避免了创build一个临时的,不会引起你关心的混乱(因为值没有被修改)。 在这里,强迫一个人传递一个指针会引起你所担心的混乱,因为看到地址被采用并传递给一个函数可能会使你认为这个值改变了。
无论如何,我基本同意你的看法。 我不喜欢函数参考来修改它们的值,当它不是很明显,这是函数正在做的事情。 在这种情况下,我也更喜欢使用指针。
当你需要返回一个复杂types的值时,我倾向于使用引用。 例如:
bool GetFooArray(array &foo); // my preference bool GetFooArray(array *foo); // alternative
在这里,函数名称清楚地表明你正在获取数组中的信息。 所以没有混淆。
引用的主要优点是它们总是包含一个有效的值,比指针更清晰,并且支持多态而不需要任何额外的语法。 如果这些优点都不适用,则没有理由偏好对指针的引用。
从维基复制 –
这样做的结果是,在许多实现中,通过引用对具有自动或静态生命周期的variables进行操作,虽然在语法上类似于直接访问它,但可能涉及隐藏的解除引用操作,这些操作是昂贵的。 引用是C ++的一个语法上有争议的特性,因为它们掩盖了标识符的间接级别; 也就是说,与指针通常在语法上突出的C代码不同,在C ++代码的大块中,如果被访问的对象被定义为本地或全局variables,或者它是一个引用(隐式指针)一些其他的位置,特别是如果代码混合引用和指针。 这方面可以使编写不好的C ++代码更难以阅读和debugging(请参阅别名)。
我同意100%,这就是为什么我相信你只有在有足够理由的情况下才使用参考。
“ 尽可能使用参考 ”规则存在问题,如果您想继续参考以供进一步使用,则会出现此问题。 为了用例子来说明这一点,假设你有下面的类。
class SimCard { public: explicit SimCard(int id): m_id(id) { } int getId() const { return m_id; } private: int m_id; }; class RefPhone { public: explicit RefPhone(const SimCard & card): m_card(card) { } int getSimId() { return m_card.getId(); } private: const SimCard & m_card; };
首先,在引用传递的RefPhone(const SimCard & card)
构造RefPhone(const SimCard & card)
使用参数似乎是一个好主意,因为它可以防止将错误/空指针传递给构造函数。 它鼓励在栈上分配variables,并从RAII中获益。
PtrPhone nullPhone(0); //this will not happen that easily SimCard * cardPtr = new SimCard(666); //evil pointer delete cardPtr; //muahaha PtrPhone uninitPhone(cardPtr); //this will not happen that easily
但是临时来破坏你的快乐世界。
RefPhone tempPhone(SimCard(666)); //evil temporary //function referring to destroyed object tempPhone.getSimId(); //this can happen
所以,如果你盲目地坚持引用,则可能会传递无效指针,以便将引用存储到被销毁对象的可能性,而这些引用基本上具有相同的效果。
编辑:请注意,我坚持的规则是“尽可能使用引用,指针,只要你必须,避免指针,直到你不能。 从最高的答复和接受的答案(其他答案也build议如此)。 虽然这应该是显而易见的,但是例子并不表明这样的引用是不好的。 然而,它们可能会被误用,就像指针一样,它们会给代码带来自己的威胁。
指针和引用之间有以下区别。
- 当传递variables时,通过引用传递看起来像通过值传递,但具有指针语义(像指针一样行为)。
- 引用不能被直接初始化为0(空)。
- 引用(引用,未引用的对象)不能被修改(相当于“* const”指针)。
- const引用可以接受临时参数。
- 本地const引用延长了临时对象的生命周期
考虑到我目前的规则如下。
- 在参数中使用将在函数范围内本地使用的参数。
- 当0(空)是可接受的参数值时使用指针,或者您需要存储参数供进一步使用。 如果0(空)是可以接受的,我将“_n”后缀添加到参数,使用守护指针(如Qt中的QPointer)或只是文件。 您也可以使用智能指针。 共享指针要比普通指针更加小心(否则可能会导致devise内存泄漏和责任混乱)。
以下是一些指导原则。
函数使用传递的数据而不修改它:
-
如果数据对象很小,比如内置的数据types或者小的结构,那么按值传递。
-
如果数据对象是数组,则使用指针,因为这是您的唯一select。 使指针成为const的指针。
-
如果数据对象的大小合适,则使用const指针或const引用来提高程序的效率。您可以节省复制结构或类devise所需的时间和空间。 使指针或引用const。
-
如果数据对象是类对象,则使用const引用。类devise的语义往往需要使用引用,这是C ++添加此function的主要原因。因此,传递类对象参数的标准方式是参考。
一个函数修改调用函数中的数据:
1.如果数据对象是内置数据types,则使用指针。 如果你发现像fixit(&x)这样的代码,其中x是一个int,这个函数打算修改x很明显。
2.如果数据对象是一个数组,则使用唯一的select:一个指针。
3.如果数据对象是结构体,则使用引用或指针。
4.如果数据对象是类对象,请使用引用。
当然,这些只是指导方针,可能有不同的select的理由。 例如,cin使用基本types的引用,以便您可以使用cin >> n而不是cin >>&n。
把我的硬币放进去,我只是做了一个testing。 一个在那个sneeky的。 我只是让g ++使用指针创build与使用引用相同的小程序的程序集文件。 在查看输出时,他们完全一样。 除了符号之外。 所以看性能(一个简单的例子)是没有问题的。
现在在指针与引用的话题。 恕我直言,我认为清晰的首要地位。 一旦我读到隐式行为,我的脚趾开始curl。 我同意一个引用不能为NULL是很好的隐式行为。
解引用NULL指针不是问题。 它会使您的应用程序崩溃并且很容易debugging。 一个更大的问题是包含无效值的未初始化指针。 这很可能会导致内存损坏,导致未定义的行为,而没有明确的来源。
这是我认为引用比指针更安全的地方。 我同意以前的一个声明,界面(应该清楚地logging下来,参见合同devise,Bertrand Meyer)将参数的结果定义为一个函数。 现在考虑到这一切,我的喜好将尽可能使用引用。
一般来说,一个成员variables不应该是一个参考,因为这没有意义。 如果您不提供赋值运算符,则会导致该类不可分配。 另外,一旦您将成员引用设置为引用某个对象,就不可能更改该成员来引用另一个对象。 引用的最恰当的用法是使用作为参考传递的函数参数。
参考文献更清洁,更易于使用,并且在隐藏信息方面做得更好。 然而,参考文献不能被重新分配。 如果您需要先指向一个对象,然后再指向另一个对象,则必须使用指针。 引用不能为空,所以如果有问题的对象有可能是空的,你就不能使用引用。 你必须使用一个指针。 如果你想自己处理对象操作,即如果你想为堆上的对象分配内存空间而不是堆栈,则必须使用指针
int *pInt = new int; // allocates *pInt on the Heap
对于指针,你需要指向某些东西,所以指针会占用内存空间。
例如,一个采用整型指针的函数将不会接受整型variables。 所以你需要先为这个函数创build一个指针。
至于参考,它不会花费内存。 你有一个整数variables,你可以把它作为参考variables传递。 而已。 你不需要专门为它创build一个引用variables。
我更喜欢使用指针。 至less很清楚你在做什么。 我有这样的感觉,因为STL及其对代码的语法影响,所以主要使用引用。 正因为如此,还有很多像标准::移动一样的C ++标准库新颖事物才能得到你想要的东西,而不是你直觉上会想到的东西。
使用引用作为最后的手段。 在堆栈或堆上分配一个实例,使用它们。
Use references for parameter scope to get the least impact. If you use reference because pointers are too hard for you then move to another language.