【问题标题】:C++ get method - returning by value or by referenceC++ get 方法 - 按值或按引用返回
【发布时间】:2011-02-25 17:15:49
【问题描述】:

我提出了一个非常简单的问题,但不幸的是我自己想不出答案。

假设我有一些数据结构来保存设置并像设置映射一样工作。 我有一个GetValue(const std::string& name) 方法,它返回相应的值。

现在我想弄清楚 - 哪种返回值方法会更好。显而易见的意思是让我的方法表现得像

std::string GetValue(const std::string& name) const

并返回对象的副本并在性能上依赖 RVO。

另一种意味着制作两种方法

std::string& GetValue(...)
const std::string& GetValue(...) const

这通常意味着复制代码或使用一些邪恶的常量强制转换来两次使用这些例程之一。

#Q

在这种情况下你会怎么做?为什么?

【问题讨论】:

  • 它们甚至在语义上都不相同 - 你应该只离开const std::string& GetValue(...) const,除非你想让事情变得非常丑陋......
  • 这个问题没有任何意义。您是否将从类中的函数GetValue 返回的内容的副本存储为成员?如果不是,那么只有一种选择:按值返回std::string

标签: c++ reference return-value return-value-optimization


【解决方案1】:

其实我可能会使用:

std::string GetValue(const std::string& name) const;
// or
const std::string* GetValue(const std::string& name) const;

void SetValue(std::string name, std::string value);

二传手:

  • SetValue 中按值传递允许编译器进行一些不能通过 const-reference 传递进行的优化,这已在 Dave Abrahams 的文章中进行了解释,"Want Speed? Pass by Value."
  • 使用Setter 通常会更好,因为您可以检查设置的值,而使用普通引用您不能保证调用者不会对数据做任何愚蠢的事情。

对于吸气剂:

  • 通过复制返回似乎是一种浪费,因为大多数时候您不会修改返回的对象(对于设置映射),但是指针或引用确实意味着别名对象将存在足够长的时间,如果您不能保证那么重点是没有意义的:按值返回。还要复制允许以避免暴露内部细节,例如,如果您突然切换到std::wstring,因为您需要一些 UTF-8 设置...
  • 如果你需要性能,那么你准备在这个部门做一些让步。但是,引用不允许您发出属性不存在的信号(除非您有一些神奇的值),而指针却很容易(NULL 已经为您剪掉了)。

【讨论】:

  • 如果我没记错的话,这些优化需要编译器支持右值引用和利用这一点的标准库实现。那么这个应该提一下。
  • 他指的是复制省略,不需要移动语义。
【解决方案2】:

这取决于使用情况。 GetValue("foo") = "bar" 应该有意义吗?在这种情况下,按值返回不会做你想要的。

【讨论】:

    【解决方案3】:

    如果您可以通过 const 引用返回,请执行此操作。按值返回像 std::string 这样的内存管理类的唯一原因是因为它是一个临时字符串。

    【讨论】:

      【解决方案4】:

      这是 C++ 非常“敏感”的一点。恕我直言,这是 C++ 设计的最薄弱环节之一。 AFAIK 没有真正好的选择,外观和性能都很好。

      应该提到一点,RVO 优化通常只完成部分工作。没有它,典型的表达是这样的:

      std::string txt = GetValue("...");
      

      实际上会产生 2 个副本! 取决于编译器,但通常 RVO 优化只会消除一个副本,但另一个仍然存在。

      【讨论】:

      • 您能否澄清您所说的“……但是另一个 [副本] 仍然存在”是什么意思?如果是这样的话,那么优化器就会创建错误的代码! AFAIK,在您展示的这种情况下,一些编译器让函数直接在分配的变量中构造返回值(在您的示例中为txt),并且永远不会有两个副本(字典中的那个不包括在该计数中) .
      • 在 C++0x 中,对于右值引用,有明确的语义可以避免在此函数的返回值中出现额外的临时副本。只有 2 个副本,一个已经在设置对象中,另一个将在 txt 变量中结束。
      【解决方案5】:

      我添加这个答案是为了避免未来对 C++ 缺乏经验的读者编写产生未定义行为的代码。

      问题没有说明Getter函数所属的类是否存储了要返回的值的副本。

      让我们首先处理按值返回的情况。这是您通常会选择的选项。

      std::string GetValue(const std::string& name) const
      

      这是有效的 C++,不会产生未定义的行为。

      OP 没有指定返回值是如何产生的。可能是这样的:

      std::string GetValue(const std::string& name) const
      {
          std::string temporary = function_of_name(name);
          // for example:
          // std::string temporary = "Hello " + name + "!";
          return temporary;
      }
      

      在上面的示例中,在GetValue 范围内创建了一个临时对象。在函数作用域的末尾,它按值返回,这意味着要么制作了对象的副本,要么编译器更有可能执行返回值优化 (RVO)(C++ 11 之前和之后)并且副本是“优化的”出”或“省略”。

      请不要将此与现代 C++ 11 及更高版本的“移动语义”混淆。这是一个不同但相关的概念。 RVO 存在于移动语义之前,大多数优秀的 C++ 03 编译器都会执行 RVO。

      关键是在这里返回任何类型的引用例如std::string&会产生未定义的行为,因为对象在函数体末尾超出范围并且返回的引用现在指向已删除的内容/从记忆中删除。 (引用只是一个应用了额外语法糖的指针。)

      第二种情况

      如果类存储要返回的对象的副本,则返回引用是一种有效的做法。但是,它可能不会比第一个 RVO 案例快很多。

      如有必要,我可以对此进行扩展,以便稍后添加更多细节,但希望现在清楚为什么会这样。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-13
        • 1970-01-01
        • 2020-03-13
        • 2018-07-29
        • 2011-11-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多