何时使用引用与指针

时间:2011-08-14 17:08:30

标签: c++ pointers reference

我理解指针与引用的语法和一般语义,但是我应该如何决定何时在API中使用引用或指针呢?

当然有些情况需要一个或另一个(operator++需要一个引用参数),但总的来说我发现我更喜欢使用指针(和const指针),因为语法很清楚变量是破坏性地通过了。

E.g。在以下代码中:

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++)?是否有任何性能问题?

编辑(已完成):

除了允许NULL值和处理原始数组之外,似乎选择归结为个人偏好。我接受了下面引用Google's C++ Style Guide的答案,因为它们提出了“引用可能令人困惑的观点,因为它们具有值语法但指针语义。”。

由于清理不应为NULL的指针参数所需的额外工作(例如add_one(0)将调用指针版本并在运行时中断),从可维护性角度来看,使用对象必须引用是有意义的虽然丢失句法清晰度是一种耻辱,但仍然存在。

17 个答案:

答案 0 :(得分:267)

尽可能使用参考,指向任何地方的指针。

在你做不到之前避免使用指针。

原因是指针比其他任何结构都更难以跟踪/阅读,安全性更低,操作更危险。

所以经验法则是只有在没有其他选择时才使用指针。

例如,当函数在某些情况下可以返回nullptr并且假设它将返回时,返回指向对象的指针是一个有效选项。也就是说,更好的选择是使用与boost::optional类似的东西。

另一个例子是使用指向原始内存的指针进行特定的内存操作。这应隐藏并本地化在代码的非常狭窄的部分,以帮助限制整个代码库的危险部分。

在您的示例中,使用指针作为参数没有意义,因为:

  1. 如果您提供nullptr作为参数,那么您将进入undefined-behavior-land;
  2. 引用属性版本不允许(不容易发现技巧)1的问题。
  3. 参考属性版本对于用户来说更容易理解:您必须提供有效的对象,而不是可以为null的对象。
  4. 如果函数的行为必须使用或不使用给定对象,那么使用指针作为属性表明您可以传递nullptr作为参数,它对于函数来说很好。这是用户和实现之间的契约。

答案 1 :(得分:57)

性能完全相同,因为引用在内部实现为指针。因此,您无需担心这一点。

关于何时使用引用和指针,没有普遍接受的约定。在少数情况下,您必须返回或接受引用(例如,复制构造函数),但除此之外,您可以根据需要自由地执行。我遇到的一个相当常见的约定是当参数必须在NULL值为ok时引用现有对象和指针时使用引用。

某些编码约定(如Google's)规定应始终使用指针或const引用,因为引用有一些不明确的语法:它们具有引用行为但值语法。

答案 2 :(得分:31)

来自C++ FAQ Lite -

  

尽可能使用引用,并在必要时使用指针。

     

每当您不需要时,引用通常优先于指针   “重新安装”。这通常意味着引用在a中最有用   class的公共接口。参考文献通常出现在皮肤上   一个对象,以及内部的指针。

     

上面的例外是函数的参数或返回   value需要一个“sentinel”引用 - 一个不引用的引用   到一个对象。这通常最好通过返回/取指针来完成,   并赋予NULL指针这一特殊意义(引用必须   始终别名对象,而不是取消引用的NULL指针)。

     

注意:旧的C语言程序员有时候不喜欢引用   它们提供了在调用者中不明确的引用语义   码。然而,在一些C ++经验之后,人们很快意识到这一点   信息隐藏的一种形式,它是一种资产而不是一种资产   责任。例如,程序员应该用语言编写代码   问题而不是机器的语言。

答案 3 :(得分:14)

我的经验法则是:

  • 使用指针输出或输入/输出参数。因此可以看出价值将会发生变化。 (您必须使用&
  • 如果NULL参数是可接受的值,则使用指针。 (确保它是const,如果它是传入参数)
  • 如果传入参数不能为NULL并且不是基本类型(const T&),则使用引用。
  • 返回新创建的对象时使用指针或智能指针。
  • 使用指针或智能指针作为结构或类成员而不是引用。
  • 使用别名参考(例如int &current = someArray[i]

无论您使用哪一种,如果不明显,请不要忘记记录您的功能及其参数的含义。

答案 4 :(得分:12)

与已经回答的其他人一样:始终使用引用,除非NULL / nullptr变量确实是有效状态。

John Carmack关于这个问题的观点是相似的:

  

NULL指针是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_ptrstd::shared_ptr都可以通过默认构造函数构造为“空”指针:

...意味着使用它们而不验证它们不是空的可能会导致崩溃,这正是J. Carmack讨论的全部内容。

然后,我们有一个有趣的问题“我们如何将智能指针作为函数参数传递?”

Jon answer代表问题C++ - passing references to boost::shared_ptr,以下评论表明,即便如此,通过复制或引用传递智能指针并不像人们那样明确喜欢(默认情况下我赞成“引用”,但我可能错了)。

答案 5 :(得分:12)

免责声明:除了引用不能为NULL也不是“反弹”(意味着它不能改变对象,它们是别名)这一事实,它真的归结为品味问题,所以我不是要说“这更好”。

那就是说,我不同意你在帖子中的最后一句话,因为我不认为代码在参考文献中失去了清晰度。在您的示例中,

add_one(&a);

可能比

更清晰
add_one(a);

因为你知道a的价值很可能会发生变化。另一方面,功能的签名

void add_one(int* const n);

有点不清楚:n是单个整数还是数组?有时您只能访问(记录不良)标题和

等签名
foo(int* const a, int b);

乍一看并不容易解释。

Imho,当不需要(重新)分配和重新绑定(在前面解释的意义上)时,引用和指针一样好。此外,如果开发人员仅使用指针作为数组,则函数签名不那么模糊。更不用说运算符语法在引用方面更具可读性这一事实。

答案 6 :(得分:7)

这不是品味问题。以下是一些明确的规则。

如果要在声明它的范围内引用静态声明的变量,那么使用C ++引用,它将是完全安全的。这同样适用于静态声明的智能指针。通过引用传递参数就是这种用法的一个例子。

如果你想引用比声明范围更宽的范围内的任何东西,那么你应该使用引用计数的智能指针来保证它的完全安全。

您可以引用集合的元素,并提供语法方便的参​​考,但它并不安全;该元素可以随时删除。

要安全地保存对集合元素的引用,您必须使用引用计数智能指针。

答案 7 :(得分:5)

任何性能差异都会很小,以至于无法使用不太清楚的方法。

首先,一个未提及引用通常优越的案例是const引用。对于非简单类型,传递const reference可以避免创建临时值,并且不会引起您关注的混淆(因为值未被修改)。在这里,强制一个人传递一个指针会引起你担心的混乱,因为看到所采用的地址并传递给一个函数可能会让你觉得这个值发生了变化。

无论如何,我基本上同意你的看法。我不喜欢函数接受引用来修改它们的值,因为这不是函数正在做的事情。在这种情况下我也更喜欢使用指针。

当您需要返回复杂类型的值时,我倾向于更喜欢引用。例如:

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

这里,函数名称清楚地表明您在数组中获取信息。所以没有混淆。

引用的主要优点是它们总是包含有效值,比指针更清晰,并且支持多态而无需任何额外的语法。如果这些优点都不适用,则没有理由比指针更喜欢引用。

答案 8 :(得分:4)

wiki -

复制
  

这样做的结果是,在许多实现中,通过引用对具有自动或静态生命周期的变量进行操作,尽管在语法上类似于直接访问它,但可能涉及隐藏的取消引用操作,这是昂贵的。引用是C ++在语法上有争议的特性,因为它们模糊了标识符的间接级别;也就是说,与指针通常在语法上突出的C代码不同,在一大块C ++代码中,如果被访问的对象被定义为本地或全局变量或者它是否是一个引用(隐式指针),它可能不会立即显而易见。其他一些位置,特别是如果代码混合引用和指针。这方面可能会使写得不好的C ++代码更难以阅读和调试(参见别名)。

我同意100%这一点,这就是为什么我认为你应该只在你有充分理由这样做时使用参考。

答案 9 :(得分:2)

" 尽可能使用引用"规则,如果你想保留参考资料以供进一步使用,就会出现这种情况。为了举例说明这一点,假设您有以下类。

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)构造函数中的参数似乎是个好主意,因为它可以防止将错误的/ null指针传递给构造函数。它以某种方式鼓励在堆栈上分配变量并从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

因此,如果你盲目地坚持引用,那么就可以通过传递无效指针来存储对被破坏对象的引用的可能性,这具有基本相同的效果。

编辑:请注意,我坚持使用规则"尽可能使用参考,指向任何地方的指针。在你不能做之​​前避免使用指针。"从最受欢迎和接受的答案(其他答案也建议如此)。虽然它应该是显而易见的,但示例并不是说这样的引用是坏的。然而,它们可能被滥用,就像指针一样,它们可以对代码带来自己的威胁。


指针和引用之间存在以下差异。

  1. 当涉及到传递变量时,按引用传递看起来像是按值传递,但是具有指针语义(就像指针一样)。
  2. 无法将引用直接初始化为0(null)。
  3. 无法修改引用(引用,未引用的对象)(相当于" * const"指针)。
  4. const引用可以接受临时参数。
  5. Local const references prolong the lifetime of temporary objects
  6. 考虑到这些,我现行的规则如下。

    • 使用将在函数范围内本地使用的参数的参考。
    • 当0(null)是可接受的参数值时使用指针,或者您需要存储参数以供进一步使用。如果0(null)可以接受我正在添加" _n"参数后缀,使用保护指针(如Qt中的QPointer)或只记录它。您还可以使用智能指针。 你必须对共享指针比使用普通指针更加小心(否则你最终会因设计内存泄漏和责任混乱而结束)。

答案 10 :(得分:2)

要记住的要点:

  1. 指针可以是NULL,引用不能是NULL

  2. 引用更易于使用,const可以用作我们不想更改值而只需要函数中引用的引用。

  3. *一起使用的指针,而与&一起使用的引用。

  4. 在需要进行指针算术运算时使用指针。

  5. 您可以具有指向无效类型int a=5; void *p = &a;的指针,但不能具有对无效类型的引用。

指针与参考

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

确定何时使用什么

指针:用于数组,链接列表,树实现和指针算术。

参考:在函数参数和返回类型中。

答案 11 :(得分:1)

以下是一些指导原则。

函数使用传递的数据而不修改它:

  1. 如果数据对象很小,例如内置数据类型或小型结构,请按值传递。

  2. 如果数据对象是数组,请使用指针,因为这是您唯一的选择。使指针成为指向const的指针。

  3. 如果数据对象是一个大小合适的结构,请使用const指针或const 参考提高程序效率。您可以节省所需的时间和空间 复制结构或类设计。使指针或引用const。

  4. 如果数据对象是类对象,则使用const引用。类设计的语义通常需要使用引用,这是C ++添加的主要原因 因此,传递类对象参数的标准方法是参考。

  5. 一个函数修改调用函数中的数据:

    1.如果数据对象是内置数据类型,请使用指针。如果您发现代码 像fixit(&amp; x),其中x是一个int,很明显这个函数打算修改x。

    2.如果数据对象是数组,请使用您唯一的选择:指针。

    3.如果数据对象是结构,请使用引用或指针。

    4.如果数据对象是类对象,请使用引用。

    当然,这些只是指导方针,可能有不同的原因 选择。例如,cin使用基本类型的引用,以便您可以使用cin&gt;&gt; ñ 而不是cin&gt;&gt; &安培; N

答案 12 :(得分:0)

在我的实践中,我个人遵循一条简单的规则-使用可复制/可移动的原语和值的引用,以及使用寿命长的对象的指针。

对于Node示例,我肯定会使用

AddChild(Node* pNode);

答案 13 :(得分:0)

您正确编写的示例应该看起来像

void add_one(int& n) { n += 1; }
void add_one(int* const n)
{
  if (n)
    *n += 1;
}

这就是为什么在可能的情况下最好使用引用 ...

答案 14 :(得分:0)

参考文献更清晰,更易于使用,并且可以更好地隐藏信息。 但是,参考不能重新分配。 如果需要先指向一个对象然后指向另一个对象,则必须使用指针。引用不能为null,因此如果存在相关对象可能为null的任何可能性,则不得使用引用。你必须使用指针。 如果你想自己处理对象操作,即如果你想为堆上的对象而不是堆栈上的对象分配内存空间,你必须使用指针

int *pInt = new int; // allocates *pInt on the Heap

答案 15 :(得分:-1)

只是把我的一角钱。我刚刚进行了测试。一个偷偷摸摸的人。我只是让g ++使用指针与使用引用相比创建相同迷你程序的程序集文件。 在查看输出时,它们完全相同。符号除了符号。所以看一下表演(在一个简单的例子中)没有问题。

现在讨论指针与引用的主题。恕我直言,我认为清晰度最重要。一旦我读到隐含的行为,我的脚趾开始卷曲。我同意引用不能为NULL是一种很好的隐式行为。

取消引用NULL指针不是问题。它会使您的应用程序崩溃并且易于调试。更大的问题是包含无效值的未初始​​化指针。这很可能导致内存损坏,导致未定义的行为,而没有明确的来源。

这是我认为引用比指针更安全的地方。我同意之前的陈述,即界面(应该清楚地记录,参见合同设计,Bertrand Meyer)定义函数参数的结果。现在考虑到这一切,我的偏好去了 随时随地使用参考文献。

答案 16 :(得分:-1)

对于指针,你需要它们指向某个东西,所以指针花费了内存空间。

例如,采用整数指针的函数不会采用整数变量。所以你需要先创建一个指针,然后传递给函数。

至于参考,它不会花费内存。您有一个整数变量,您可以将其作为引用变量传递。而已。您无需专门为其创建引用变量。