【问题标题】:When pass-by-pointer is preferred to pass-by-reference in C++?在 C++ 中,什么时候传递指针优于传递引用?
【发布时间】:2011-02-02 18:28:51
【问题描述】:

我可以想象一种情况,其中输入参数可以为 NULL,因此首选传递指针而不是传递引用?

任何人都可以添加更多案例吗?

【问题讨论】:

  • 似乎无法想到任何 有用 的情况,即通过指针传递优于通过引用传递,除了您指出传递 NULL 实际上意味着什么的情况.

标签: c++


【解决方案1】:

在处理原始内存时(例如,如果创建您自己的内存池),您会希望使用指针。但你是对的,在普通代码中,指针的唯一用途是可选参数。

【讨论】:

    【解决方案2】:

    在传递的对象实际上要被修改的情况下,有些人更喜欢按指针传递。当对象通过引用传递时,它们使用 pass-by-const-reference 以避免对象的副本,但不会在函数中更改。

    在说明中,取以下函数:

    int foo(int x);
    int foo1(int &x);
    int foo2(int *x);
    

    现在在代码中,我执行以下操作:

    int testInt = 0;
    
    foo(testInt);   // can't modify testInt
    foo1(testInt);  // can modify testInt
    
    foo2(&testInt); // can modify testInt
    

    在调用 foo 与 foo1 时,从调用者的角度(或阅读代码的程序员)来看,函数可以修改 testInt 而无需查看函数的签名并不明显。看 foo2 ,读者可以很容易地看到该函数实际上可能会修改 testInt 的值,因为该函数正在接收参数的地址。请注意,这并不能保证对象被实际修改,但这就是在使用引用和指针时保持一致的地方。一般来说,如果您想始终遵循此准则,则应始终在希望避免复制时传递 const 引用,并在希望能够修改对象时通过指针传递。

    【讨论】:

    • 这是一个非常好的观点。你完全正确。作为一名程序员,我通常不会期望foo1(testInt) 修改 foo1,如果在函数调用之后 testInt 不同,我会感到有点惊讶(尽管我最终会通过检查函数定义来弄清楚)。但是对于foo2(&testInt),很明显 testInt 可用于函数修改。优秀的答案。我认为这是一个特别重要的标准,在诸如教科书之类的自动完成(将显示函数定义)等工具不可用的情况下采用。
    • 你忘了提到我可能想通过NULL 的情况,无论const 与否(你应该在讨论中提到的另一件事)期望被调用者修改或不修改事物并采取额外步骤来强制执行,如果我们要走那条路线。) :) 最重要的是,您忘记提及 operator= 对指针与引用的影响!
    • 我发现“你知道它是否可以改变”的论点是一个弱论点。为什么要编写如此不透明的函数,以至于需要猜测将修改哪些参数?对于最初的写作,你必须有可用的定义来告诉你哪些需要是指针,所以很明显它在那里没有多大作用。
    • 我认为引用的“不能为NULL”属性在函数签名中比函数是否可能修改引用(或指向)参数更重要。因为该语言允许通过引用传递到可修改的对象,所以您永远不会受到约定的完全保护。当您使用指针表示可能为空时,它会准确记录您的检查应该在哪里。如果在任何地方都使用指针,则必须记录指针必须不为空的位置作为前提条件(不太健壮),或者向许多不需要它们的函数添加额外的空检查。
    • 我倾向于同意你们中的大多数人的观点,即我宁愿拥有引用的“不能为 NULL”属性,而不是“无法更改”,这就是我不这样做的原因尽管我可以看到它的好处,但我自己也不会遵循这个约定。另一方面,Google 认为知道对象是否可以更改更为重要,因为它确实遵循这些约定。见:google-styleguide.googlecode.com/svn/trunk/…
    【解决方案3】:

    在 C++ 中,大多数情况下几乎不需要通过指针传递。您应该首先考虑替代方案:模板、(const)引用、容器、字符串和智能指针。也就是说,如果您必须支持遗留代码,那么您将需要使用指针。如果您的编译器是简约的(例如嵌入式系统),您将需要指针。如果您需要与 C 库(您正在使用的非常特殊的驱动程序的某种系统库?)交谈,那么您将需要使用指针。如果你想处理非常特殊的内存偏移量,那么你需要指针。

    在 C 中,指针是一等公民,它们太基础了,无法考虑消除它们。

    【讨论】:

      【解决方案4】:

      可以想象,可以编写一个函数来对内存做一些事情,例如重新分配它以使其更大(返回新指针)。

      【讨论】:

        【解决方案5】:

        任何时候你传递一个函数指针。 或者,如果你有一个“重置”方法,比如 auto_ptr。

        【讨论】:

          【解决方案6】:

          如果我需要传递一个对象数组,我需要通过指针传递。数组并不总是存储在std::vector<>

          除此之外,按指针传递允许 NULL 而按引用传递则不允许,因此我将这种区别用作合同,就像 SQL 中的 NULLNOT NULL 列一样,大致类似于“函数返回 @987654324 @:true = 成功,false = 失败,而函数返回 int:返回值是一个结果代码,其中0 = 成功,其他都是失败。

          【讨论】:

          • 嗯,动态数组应该始终存储在vector 中。也就是说,这是一个问题中的漏洞:如果您打算修改指针,您会使用双指针还是引用? (问题不在于参数列表中的 a 指针,而在于当您需要在函数中修改指针与引用时。在您的情况下,指针只是副产品。)
          • 我已经编写了大量代码来处理未存储在vector 中的数据,而且不应该这样。当您获得一个数据包时,您希望直接对该数据进行操作,而不是浪费时间调整向量大小并将数据复制到其中。此外,您可以使用指针轻松地进行转置操作或跨步(每个第 n 个元素)操作。 BLAS 类型的代码本质上永远不会使用vector;如果您尝试,您将有很多&vec[0] 代码。 (强迫一切都存在于vectors 是 C++ 被不公平地指控导致代码膨胀的原因之一。如果你不需要它,请不要使用它。)
          • @Mike:怎么样:vector<char> buffer(bufferSize); recv(&buffer[0], buffer.size()); 等等?如果要将数据复制到向量中,为什么不直接将其填充到向量中呢?当然,在使用旧样式代码时,“使用矢量”的建议就没有了。在新代码中,我从未遇到过处理原始内存的理由。 (说真的,向量只是这样做,但确保它被删除就是全部。对于原始内存分配,没有什么是你不能用向量做的。)
          • @GMan 是有原因的。我可以在堆栈上分配我的缓冲区,而不会因为堆分配/释放而杀死自己(非平凡的,缓存的不良位置,并且可能涉及锁定。)
          • @Vlad:静态分配的数组在哪里发挥了作用?请注意我的第一句话:“嗯,动态数组应该...”。显然,如果一个静态数组可以,使用一个。
          【解决方案7】:

          在现实世界的编程中有很多情况,其中参数不存在或无效,这可能取决于代码的运行时语义。在这种情况下,您可以使用 NULL (0) 来表示此状态。除此之外,

          • 可以将指针重新分配给新的 状态。参考不能。这是 在某些情况下是可取的。
          • 指针有助于转移所有权 语义。这个特别有用 如果在多线程环境中 参数状态用于执行 一个单独的线程,你没有 通常轮询直到线程有 已退出。

          虽然如果花足够的时间正确设计代码,这些情况是可以避免的;实际上,不可能每次都这样做。

          【讨论】:

            【解决方案8】:

            除了关于所有权语义(尤其是工厂函数)的一些其他答案。

            虽然不是技术原因,但常见的样式指南要求是任何可以修改的参数都应该通过指针传递。这使得在调用点很明显可以修改对象。

            void Operate(const int &input_arg, int *output_arg) {
              *output_arg = input_arg + 1;
            }
            
            int main() {
              int input_arg = 5;
              int output_arg;
              Foo(input_arg, &output_arg);  // Passing address, will be modified!
            }
            

            【讨论】:

            • 您会将此风格指南推广到所有参数,还是主要推广到 POD 类型参数?
            【解决方案9】:

            当您需要操作(例如调整大小、重新分配等)函数内部指针所指向的地址时,您需要一个指针。

            例如:

            
            void f( int* p )
            {
                // ..
            
                delete []p;
                p = new int[ size ];
            
                //...
            }
            

            与引用不同,指针的值可以更改和操作。

            【讨论】:

            • 我会猛烈抨击任何删除我的指针的人!如果您想操作内存,请在界面中明确说明您将使用auto_ptr 或更好的unique_ptr 来获得所有权。
            【解决方案10】:

            这不是专门的参数传递,但它确实会影响参数传递。

            您可以拥有指针的容器/聚合,但不能拥有引用。尽管引用是多态的,但只有指针支持对容器使用至关重要的“更新”操作(特别是因为您还不能批量初始化引用,不确定 C++0x 聚合初始化器是否会改变这一点)。

            因此,如果您有一个充满指针的容器,您通常最终会使用接受指针而不是引用的函数来管理它。

            【讨论】:

            • +1 表示其他使用指针的有趣点,即使内部需要是指针,接口仍然可以是引用(没有理由在方法接口和存储中没有引用容器或成员属性中对象的地址)。
            • 查找Boost.Pointer Containers 库。我只希望“索引”案例存在没有所有权的版本:/
            • @dribeas:感谢您说明绝对不应使用 IMO 引用的另一种情况:当指针或参数引用的保留时间超过函数调用的持续时间时。在这种情况下,传递引用鼓励使用局部变量(或者更糟糕的是,const 引用可以绑定到 temporaries)。如果您打算以任何方式存储参数的地址,请让调用者明确地传递该地址。
            • 你仍然可以获得被引用参数的地址(这也是引用本身的地址),或者你可以使用 std::reference_wrapper。
            • @Matthias:不,没有办法获得参考的地址。它是透明的,应用& 运算符将对目标对象执行它。虽然您可以使用引用参数来查找目标的地址,但我仍然强烈地感觉到我之前评论中的指导“如果您要以任何方式存储参数的地址,请让调用者明确地传递该地址"是正确的。
            【解决方案11】:

            C++ FAQ 对这个问题有很好的回答:

            尽可能使用参考文献,并且 必要时指点。

            参考文献通常优先于 不需要时的指针 “重新安置”。这通常意味着 参考是最有用的 类的公共接口。参考 通常出现在皮肤上 对象和内部的指针。

            上述情况的例外是 函数的参数或返回值 需要一个“哨兵”参考——一个 不指代的参考 目的。这通常最好由 返回/获取指针,并给出 这个特殊的NULL指针 意义(参考应始终 别名对象,而不是取消引用的 NULL 指针)。

            注意:老 C 程序员有时 不喜欢参考,因为他们 提供不是的参考语义 显式在调用者的代码中。后 一些 C++ 经验,但是,一个 很快意识到这是一种形式 信息隐藏,这是一种资产 而不是责任。例如。, 程序员应该在 问题的语言而不是 机器的语言。

            【讨论】:

              【解决方案12】:

              规则一:如果 NULL 是函数上下文中函数参数的有效值,则将其作为指针传递,否则作为引用传递。

              基本原理,如果它不能(不应该!)永远是 NULL,那么不要让自己经历检查 NULL 的麻烦或因为它是 NULL 而冒着问题的风险。

              【讨论】:

              • 或者根本不通过。通常,您可以简单地提供一个只接受有用参数的重载。
              • @Dennis:是和否,您可能希望以统一的方式处理这两种情况:您正在迭代指针容器,或者传递由另一个函数返回的指针...跨度>
              【解决方案13】:

              Google 似乎对此有强烈的看法,我倾向于同意:

              http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Reference_Arguments

              【讨论】:

              • 您的链接已损坏
              猜你喜欢
              • 2013-12-24
              • 2015-02-11
              • 2010-10-10
              • 2021-11-12
              • 2010-09-24
              • 2013-01-13
              • 2010-10-23
              • 2020-03-14
              • 2012-03-03
              相关资源
              最近更新 更多