关于c ++:何时使用引用与指针

When to use references vs. pointers

我了解指针与引用之间的语法和一般语义,但是我应该如何决定在API中使用引用或指针是更合适还是更不合适?

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

例如,在以下代码中:

1
2
3
4
5
6
7
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++)?其中一个或另一个存在性能问题吗?

编辑(过时):

除了允许空值和处理原始数组之外,似乎选择取决于个人偏好。我接受了下面的答案,引用谷歌的C++风格指南,因为他们提出"引用可能会混淆,因为它们有值语法但指针语义。"

由于对不应为空的指针参数进行消毒所需的额外工作(例如,add_one(0)将在运行时调用指针版本并中断),因此从可维护性的角度来看,在必须存在对象的地方使用引用是有意义的,尽管丢失语法清晰性是一种耻辱。


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

避免使用指针,直到不能使用为止。

原因是指针使操作变得比任何其他构造都更难执行/读取,更不安全,更危险。

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

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

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

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

  • 如果你提供nullptr作为论据,你将进入一个不明确的行为领域;
  • 引用属性版本不允许(没有容易发现的技巧)1出现问题。
  • 对于用户来说,引用属性版本更容易理解:必须提供有效的对象,而不是可能为空的对象。
  • 如果函数的行为必须使用或不使用给定的对象,那么使用指针作为属性建议您可以将nullptr作为参数传递,这对函数来说是很好的。这是用户和实现之间的一种契约。


    性能完全相同,因为引用是作为指针在内部实现的。所以你不必担心。

    关于何时使用引用和指针,没有公认的惯例。在一些情况下,您必须返回或接受引用(例如,复制构造函数),但除此之外,您可以自由地执行您希望的操作。我遇到的一个非常常见的约定是,当参数必须引用现有对象时使用引用,当空值正常时使用指针。

    有些编码约定(如Google)规定应该始终使用指针或常量引用,因为引用的语法有点不清楚:它们有引用行为,但有值语法。


    来自C++ FAQ Lite——

    Use references when you can, and pointers when you have to.

    References are usually preferred over pointers whenever you don't need
    "reseating". This usually means that references are most useful in a
    class's public interface. References typically appear on the skin of
    an object, and pointers on the inside.

    The exception to the above is where a function's parameter or return
    value needs a"sentinel" reference — a reference that does not refer
    to an object. This is usually best done by returning/taking a pointer,
    and giving the NULL pointer this special significance (references must
    always alias objects, not a dereferenced NULL pointer).

    Note: Old line C programmers sometimes don't like references since
    they provide reference semantics that isn't explicit in the caller's
    code. After some C++ experience, however, one quickly realizes this is
    a form of information hiding, which is an asset rather than a
    liability. E.g., programmers should write code in the language of the
    problem rather than the language of the machine.


    我的经验法则是:

    • 将指针用于传出或输入/输出参数。所以可以看出,这个值将会改变。(您必须使用&)
    • 如果可以接受空参数值,则使用指针。(如果是传入参数,请确保它是const)
    • 如果传入参数不能为空且不是基元类型(const T&),则对其使用引用。
    • 返回新创建的对象时使用指针或智能指针。
    • 使用指针或智能指针作为结构或类成员,而不是引用。
    • 使用引用作为别名(例如int ¤t = someArray[i])

    无论您使用哪一个,如果您的函数及其参数不明显,不要忘记记录它们的意义。


    像其他人已经回答过的那样:总是使用引用,除非变量为NULL/nullptr确实是有效状态。

    约翰·卡马克对这个问题的看法类似:

    NULL pointers are the biggest problem in C/C++, at least in our code. The dual use of a single value as both a flag and an address causes an incredible number of fatal issues. C++ references should be favored over pointers whenever possible; while a reference is"really" just a pointer, it has the implicit contract of being not-NULL. Perform NULL checks when pointers are turned into references, then you can ignore the issue thereafter.

    http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

    编辑2012-03-13

    用户Bret Kuhns正确评论:

    The C++11 standard has been finalized. I think it's time in this thread to mention that most code should do perfectly fine with a combination of references, shared_ptr, and unique_ptr.

    确实如此,但即使用智能指针替换原始指针,问题仍然存在。

    例如,std::unique_ptrstd::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所讨论的。

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

    乔恩对C++问题的回答——通过对Booo::SydDypTR的引用,以及下面的注释显示,即使在那时,通过复制或引用传递智能指针并不像人们所希望的那样清晰(我默认自己的"引用",但我可能错了)。


    免责声明:除了引用不能为空或"反弹"(意思是它们不能改变它们的别名对象)之外,它实际上是一个品味问题,所以我不会说"这更好"。

    也就是说,我不同意你在文章中最后的陈述,因为我认为代码不会因为引用而失去清晰度。在你的例子中,

    1
    add_one(&a);

    可能比

    1
    add_one(a);

    因为你知道A的值很可能会改变。另一方面,函数的签名

    1
    void add_one(int* const n);

    也有点不清楚:n是一个整数还是一个数组?有时您只能访问(文档记录不好)头文件和签名,例如

    1
    foo(int* const a, int b);

    一眼就不容易理解。

    imho,当不需要(重新)分配或重新绑定(在前面解释的意义上)时,引用和指针一样好。此外,如果开发人员只对数组使用指针,那么函数签名就不那么模棱两可了。更不用说运算符语法对引用的可读性更高了。


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

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

    如果要引用范围中比声明范围更宽的任何内容,则应使用引用计数的智能指针,使其完全安全。

    为了方便语法,可以引用集合中的某个元素,但这是不安全的;可以随时删除该元素。

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


    任何性能差异都是如此之小,以至于使用不那么清晰的方法是不合理的。

    首先,一个没有提到的案例是const参考文献,其中参考文献通常更优。对于非简单类型,传递const reference可以避免创建临时类型,并且不会引起您所关心的混乱(因为值没有被修改)。在这里,强制一个人传递一个指针会引起你担心的非常混乱,因为看到获取的地址并将其传递给一个函数可能会让你认为值发生了变化。

    无论如何,我基本同意你的看法。我不喜欢函数引用来修改它们的值,因为函数的作用并不明显。在这种情况下,我也更喜欢使用指针。

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

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

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

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


    从wiki复制-

    A consequence of this is that in many implementations, operating on a variable with automatic or static lifetime through a reference, although syntactically similar to accessing it directly, can involve hidden dereference operations that are costly. References are a syntactically controversial feature of C++ because they obscure an identifier's level of indirection; that is, unlike C code where pointers usually stand out syntactically, in a large block of C++ code it may not be immediately obvious if the object being accessed is defined as a local or global variable or whether it is a reference (implicit pointer) to some other location, especially if the code mixes references and pointers. This aspect can make poorly written C++ code harder to read and debug (see Aliasing).

    我百分之百同意这一点,这就是为什么我认为只有当你有充分的理由这样做的时候,你才应该使用一个参考。


    "尽可能使用引用"规则存在问题,如果要保留引用以供进一步使用,则会出现此问题。为了用示例说明这一点,假设您有以下类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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)构造函数中通过引用传递参数似乎是一个好主意,因为它可以防止向构造函数传递错误/空指针。它以某种方式鼓励在堆栈上分配变量,并从RAII中获益。

    1
    2
    3
    4
    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

    但是,暂时性的东西会摧毁你的幸福世界。

    1
    2
    3
    RefPhone tempPhone(SimCard(666));   //evil temporary
    //function referring to destroyed object
    tempPhone.getSimId();    //this can happen

    因此,如果您盲目地坚持引用,那么就权衡了传递无效指针的可能性,以换取存储对已销毁对象的引用的可能性,这基本上具有相同的效果。

    编辑:请注意,我坚持"尽你所能地使用引用,尽你所能地使用指针"的原则。避免使用指针,直到你不能这样做。"从最乐观和接受的答案(其他答案也建议这样)。尽管这应该是显而易见的,但示例并没有显示这样的引用是不好的。但是,它们可能被误用,就像指针一样,它们会给代码带来自己的威胁。

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

  • 当涉及到传递变量时,传递引用看起来像传递值,但具有指针语义(类似于指针)。
  • 引用不能直接初始化为0(空)。
  • 不能修改引用(引用,不引用对象)(相当于"*const"指针)。
  • 常量引用不能接受临时参数。
  • 局部常量引用延长了临时对象的生存期
  • 考虑到这些,我目前的规则如下。

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


    要记住的要点:

  • 指针可以是NULL,引用不能是NULL

  • 引用更容易使用,当我们不想更改值,只需要函数中的引用时,可以使用const作为引用。

  • 用于*的指针,而用于&的引用。

  • 需要指针算术运算时使用指针。

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

  • 指针与引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    void fun(int *a)
    {
        cout<<a<<'
    '
    ; // address of a = 0x7fff79f83eac
        cout<<*a<<'
    '
    ; // value at a = 5
        cout<<a+1<<'
    '
    ; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
        cout<<*(a+1)<<'
    '
    ; // value here is by default = 0
    }
    void fun(int &a)
    {
        cout<<a<<'
    '
    ; // reference of original a passed a = 5
    }
    int a=5;
    fun(&a);
    fun(a);

    判决何时使用什么

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

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


    以下是一些指导原则。

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

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

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

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

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

  • 函数修改调用函数中的数据:

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

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

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

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

    当然,这些只是指导方针,可能有理由使选择。例如,CIN使用基本类型的引用,这样您就可以使用CIN>>n而不是CIN>>&N。


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

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

    把我的一角钱放进去。我刚做了一个测试。对那件事嗤之以鼻。我只是让g++使用指针创建同一个小程序的汇编文件,而不是使用引用。当查看输出时,它们完全相同。除了符号命名。所以看性能(在一个简单的例子中)没有问题。

    现在讨论指针和引用的主题。我觉得清晰是最重要的。我一看到暗示行为,我的脚趾就开始弯曲。我同意引用不能为空是一种很好的隐式行为。

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

    这就是我认为引用比指针更安全的地方。我同意前面的一个声明,即接口(应该清楚记录,见合同设计,BertrandMeyer)定义了函数参数的结果。考虑到这一切,我的偏好转到尽可能使用参考资料。


    指针和引用具有相同的速度,指针可以做引用可以做的任何事情。它在很大程度上是基于个人的意见。使用引用是很常见的,除非您确实需要指针的一些特性。


    对于指针,您需要它们指向某个对象,因此指针占用内存空间。

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

    作为参考,它不会占用内存。您有一个整数变量,可以将其作为引用变量传递。就是这样。您不需要专门为它创建引用变量。