我了解指针和引用的语法和一般语义,但是我应该如何决定什么时候在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)将调用指针版本并在运行时中断),从可维护性的角度来看,在必须存在对象的地方使用引用是有意义的,尽管失去语法的清晰性是一种遗憾。


当前回答

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

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

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

这就是我认为引用比指针安全得多的地方。我同意前面的说法,接口(应该清楚地记录,参见合同设计,Bertrand Meyer)定义了函数参数的结果。考虑到这些因素,我的偏好是 尽可能使用引用。

其他回答

尽可能使用引用,必须使用指针。

避免指针,直到你不能。

原因是指针使事情更难跟踪/阅读,比任何其他结构更不安全,更危险的操作。

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

例如,当函数在某些情况下可以返回nullptr时,返回指向对象的指针是一个有效的选项,并且假设它将会返回。也就是说,更好的选择是使用类似于std::optional(需要c++ 17;在此之前,有boost::optional)。

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

在你的例子中,使用指针作为参数是没有意义的,因为:

如果你提供nullptr作为参数,你将进入未定义行为领域; 引用属性版本不允许(没有容易发现的技巧)1的问题。 对于用户来说,引用属性版本更容易理解:您必须提供一个有效的对象,而不是可以为空的东西。

如果函数的行为必须在有或没有给定对象的情况下工作,那么使用指针作为属性表明您可以将nullptr作为参数传递,这对函数来说是好的。这是用户和实现之间的一种契约。

两者的性能完全相同,因为引用是作为指针在内部实现的。因此你不需要担心这个。

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

一些编码约定(如谷歌的)规定应该总是使用指针或const引用,因为引用有一点不清楚的语法:它们有引用行为但有值语法。

“尽可能使用参考文献”规则有问题,如果你想保留参考文献供进一步使用,就会出现这种问题。为了举例说明这一点,假设您有以下类。

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中获益。

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

因此,如果你盲目地坚持引用,你就在传递无效指针的可能性与存储已销毁对象引用的可能性之间进行了权衡,这基本上是相同的效果。

edit:请注意,我坚持了这条规则:“尽可能使用引用,必须使用指针。”避免指针,直到你做不到为止。”这是被点赞最多、接受度最高的答案(其他答案也这么认为)。虽然这应该是显而易见的,但例子并不表明引用是不好的。然而,它们也可能被滥用,就像指针一样,它们也会给代码带来威胁。


指针和引用之间有以下区别。

当涉及到传递变量时,按引用传递看起来像按值传递,但具有指针语义(充当指针)。 引用不能直接初始化为0 (null)。 引用(引用,未引用对象)不能修改(相当于“* const”指针)。 Const引用可以接受临时形参。 局部const引用延长了临时对象的生存期

考虑到这些因素,我目前的规则如下。

Use references for parameters that will be used locally within a function scope. Use pointers when 0 (null) is acceptable parameter value or you need to store parameter for further use. If 0 (null) is acceptable I am adding "_n" suffix to parameter, use guarded pointer (like QPointer in Qt) or just document it. You can also use smart pointers. You have to be even more careful with shared pointers than with normal pointers (otherwise you can end up with by design memory leaks and responsibility mess).

从 C++ 常见问题精简版 -

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.

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

例如,接受整数指针的函数将不接受整数变量。因此,您需要首先为它创建一个指针,以便传递给函数。

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