【问题标题】:C++ "Best" Parameter Passing Method [closed]C ++“最佳”参数传递方法[关闭]
【发布时间】:2011-10-18 18:04:45
【问题描述】:

我今天正在编写一个 C++ 类,并且我编写了一个函数,该函数将参数作为引用而不是指针,这是我很少做的事情。我总是通过指针。所以我正要改回来,然后我意识到 - 我不知道我是否应该这样做,或者它是否重要。

所以我转向你们。我有三种传递参数的方式:

//1: By pointer
Object* foo(Object* bar) {…}

//2: By reference
Object& foo(Object& bar) {…}

//3: By value (just for completeness)
Object foo(Object bar) {…}

假设 #3 出于性能原因而退出(是的,我知道编译器在这方面做得很好,但仍然如此),其他两个或多或少是等价的。

那么:“最好”的方法是什么?指针?参考?两者的某种组合?或者它甚至重要吗?技术原因是最好的,但风格原因同样好。

更新:我已经接受了 YeenFei 的回答,因为它处理了对我来说很重要的差异(即使我后来故意忽略了他的建议 - 我喜欢将 NULL 作为一个选项...... )。但是每个人都提出了很好的观点 - 特别是 GMan(在 cmets 中)和 Nemo,在处理性能和传递价值的答案中。如果您在这里寻求答案,请全部查看!

【问题讨论】:

  • 由于您省略了函数体,我猜您认为它无关紧要。我向你保证不是。
  • 最好的什么?如果要引用对象,请使用引用。如果您想指向一个对象,或者可能根本没有对象(null),请使用指针。如果您想要自己的副本,请使用值。
  • Pointer vs. Reference 的可能重复项
  • 无论有什么原因,性能都不是其中之一。
  • 不同意性能不是问题。实际上,对于小对象,#3 几乎肯定会最快...别名问题往往会导致指针和引用生成比值更慢的代码。通常要慢得多。

标签: c++ performance coding-style parameter-passing


【解决方案1】:

如果预期有效,我建议通过引用传递您的论点。这将是一个设计优化,让您免于defensive programming

引用不能为空,而指针可以。 如果您正在处理指针,则需要在使用它们之前验证给定指针是否有效(非空),无论它是原始形式还是包装在托管容器(shared_ptr)中。

【讨论】:

  • 实际上,如果您不采取措施防止引用,则引用可能为空(即错误)。但是您关于不必检查 NULL 引用的观点是有效的。见stackoverflow.com/questions/57483/…
  • 是的,但是当函数触发访问冲突 0x00000000 (POD) 或 0x00000004 (w/ vtbl) 时,您可以轻松发现它们 :)
  • @Mark:我们之前已经讨论过这个问题。有效程序中的引用不能为空,指针可以。没有“实际上”。引用也不能“为”null,因为您无法获取 null 的地址。
  • 在 C++ 中,我们鼓励使用参考。 “尽可能使用引用,必要时使用指针”
  • @GMan,你最近看到那个链接了吗?我已经对其进行了编辑,以明确表明这种情况是由错误的程序引起的。我在生产代码中遇到过这种情况,我将继续抓住每一个机会来拯救别人我所经历的痛苦。附言我在这里提出它是因为它是到处使用引用的哲学的自然结果。
【解决方案2】:

所以我将为选择 #3 提出理由。考虑以下代码:

struct Foo {
    int x;
    int y;
};

Foo
add(Foo a, Foo b)
{
    Foo result;
    result.x = a.x + b.x;
    result.y = a.y + b.y;
    return result;
}

Foo
add2(Foo &a, Foo &b)
{
    Foo result;
    result.x = a.x + b.x;
    result.y = a.y + b.y;
    return result;
}

尝试检查生成的程序集。注意add 几乎完全是注册操作,安排得很好。请注意add2 是如何在没有任何重新排序的情况下进行大量内存访问的。

我写了一个main,它调用了这些函数中的每一个100亿次。结果? add 用了 22 秒,而add2 用了 26 秒。即使对于这个简单的示例,按值传递版本的性能也提高了 10-20%

好的,所以结构很简单。但功能也是如此。函数越复杂,传值版本越有可能更快,因为编译器知道这两个参数不会“重叠”。这对优化来说是一个巨大的好处。

当然,这个决定应该主要基于函数的语义:你需要 NULL 作为合法值吗?如果是这样,显然你需要一个指针。您需要修改对象吗?然后使用指针或引用。

但是,如果您不需要修改对象,最好通过值传递它们,除非对象很大和/或具有非平凡的复制构造函数(例如 std::string)。如果 by-value 真的太慢,请通过 reference-of-const 或 pointer-to-const 传递。

但不要低估按值传递的潜在速度优势,这源于寄存器与内存和指令重新排序的优势。请注意,这些优势会随着每一代 CPU 变得更加明显。

【讨论】:

  • 您是否也尝试过在发布模式下检查程序集差异?
  • (顺便说一句,C++ 不需要详细的类型说明符。)
  • @GMan:好点子。我的坏习惯……每当我决定使用“struct”时,我都觉得自己处于“C 模式”。修复。
  • @YeenFei:这是“发布模式”...我的意思是“值”允许指针/引用可以禁止的优化。
  • 如果您使用const Foo& 而不是Foo&,您会得到任何更改吗?
【解决方案3】:

指针传递和引用传递实际上是一样的,只是语法不同。我更喜欢通过指针传递,因为它使事情变得明确:

Object bar;
ptr_foo(&bar); // bar may change

ref_foo(bar); // can bar change? Now I need to go look at the prototype...

val_foo(bar); // bar cannot change. (Unless you use references here and there)

正如您所提到的,传递值和指针之间的唯一技术偏好是类是否足够大以使其传递缓慢。

【讨论】:

  • 好吧,复制构造一个给定的对象总是比指针/引用慢。
  • @YeenFei,如果对象不包含太多数据(例如单个int),按值传递会更快。
  • @Mark Ransom,这是一个有趣的观点(尽管我们都知道设计的对象大多数时候都比这大得多)。你介意给我指出解释原因的文章吗?
  • 如果你的程序是正确的,除了每个接受指针的函数都必须处理空值。
  • @YeenFei:你完全错了。尽管通过自身的参数可能更快,但生成的代码几乎肯定会更糟,而且会弥补它。由于别名问题而导致过多的内存加载/存储是 C++ 优化的致命弱点。
【解决方案4】:

如果您自己设计所有内容,请随时参考。惯用的现代 C++ 几乎不应该在任何地方出现原始指针。动态分配的对象应该在资源管理容器(shared_ptrunique_ptr,或weak_ptr,如果适用)中移动,但对于大多数通过 (const) 传递的操作,引用是传递需要修改的参数的主要方式,或者属于重量级的。不要忘记,如果您有可移动类型,按值传递可能是一个可行的选择。

【讨论】:

  • 我认为原始指针是可以的,如果你永远不必担心删除它们。
【解决方案5】:

用途:

  1. 对象未修改时的常量引用
  2. 对象被修改或可以为空的指针
  3. 如果对象很小并且您关心性能或者如果您需要函数内的对象副本,则该值。这允许编译器选择复制/移动参数的最佳方式。
  4. std::unique_ptr 如果所有权转移给函数。

【讨论】:

    【解决方案6】:

    您可以查看https://www.boost.org/doc/libs/1_51_0/libs/utility/call_traits.htm 库,它会自动将类型转换为最佳参数类型。

    【讨论】:

      猜你喜欢
      • 2022-01-25
      • 1970-01-01
      • 2017-03-28
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-06
      • 1970-01-01
      相关资源
      最近更新 更多