【问题标题】:Is returning by reference of a member variable EVER acceptable?通过引用成员变量返回是否可以接受?
【发布时间】:2014-12-19 18:41:00
【问题描述】:

假设我有:

class Metadata {
    // stores expensive-to-copy data, provides complex interface to access/modify
}

class SomeObject
public:
    Metadata& GetMetadata() { return mMetadata; }
private:
    Metadata mMetadata;
}

boost::shared_ptr<SomeObject> obj = ...;
obj->GetMetadata().SetTitle("foo");
obj->GetMetadata().GetTitle();

普遍的共识似乎是通过引用返回,尤其是非常量,除了少数特定情况外,在所有情况下都非常糟糕。但是,在这种情况下,它似乎是最好的(也是唯一的?)选择:

  1. 我不想退回副本,因为我想修改原件(即使是 r/o,复制也很昂贵)。
  2. 我不想返回 Metadata* 或类似的东西,因为我不想鼓励存储或传递指针。
  3. 我不想通过对元数据的无关包装调用来污染 SomeObject 的接口。

它引入了一个模糊的要求,即 SomeObject 能够通过引用传回 Metadata,这意味着必须有一个 Metadata 对象,其生命周期与 SomeObject 的生命周期相同(或长于)——然而,这就是它们之间的确切语义关系对象,并且要求肯定比上述任何选项都好。

我突然想到我可以引入某种不可复制的指针类型并返回它,但这闻起来很有趣/似乎有点矫枉过正。我可以让 SomeObject 拥有一个 boost::shared_ptr,但这确实不适合我想到的语义(基本上:如果您现在正在修改,请修改原始 - 如果您稍后阅读/存储,制作副本并自行跟踪)。

这里有更好的模式吗?我会以某种我看不到的方式在自己的脚上开枪吗?

【问题讨论】:

  • 谁告诉你这很糟糕?返回对类数据成员的引用是一种普遍且有用的编程技术。
  • 我认为返回非常量引用没有问题。虽然将成员变量公开可能更简单。
  • 鉴于Metadata 已经封装了原始数据,将mMetadata 设为SomeObject 的公共成员(最好使用更好的名称)并直接使用它也可能完全没问题通过GetMetadata() (在这种情况下似乎没有完成任何事情)。
  • @KerrekSB Scott Meyers 做了(有效的 C++ 项目 28)。虽然他确实说有像矢量这样的例外。
  • Metadata 的包装器调用可能是不必要的,但它们不应该是“不相关的”,否则Metadata 不属于SomeObject

标签: c++ design-patterns reference ownership


【解决方案1】:

返回一个比函数调用寿命更长的引用是完全可以的。当您返回对类数据成员的左值引用时,只需确保类实例本身就是一个左值:

struct Foo
{
    X data;

    X & the_data() &   { return data; }
//                ^^^

当您使用它时,如果您的实例是右值,您也可以将数据作为右值返回:

    X && the_data() && { return std::move(data); }
};

【讨论】:

  • @T.C.:哦,是的。或std::move(*this).data :-) 更惯用(将绑定到右值的事物移动)。
  • 当然,通过const X&amp;返回以避免不必要地复制返回值也很常见。
  • TIL 你可能需要这样的左值。我每天都会学到一些关于 C++ 的新知识。
  • 谢谢 - 不知道 l/rvalue 用法的种类。
【解决方案2】:

当然,只要您不返回对本地(自动)存储的引用,就性能而言,返回引用是首选。在大多数情况下,您应该返回 const 引用。

这允许 C++ 编译器(它是一种在设计上积极优化的语言编译器)优化掉很多东西,还允许编写“流利”的语法。

obj.foo().bar().baz();

【讨论】:

  • 我不同意。我认为您实际上应该有充分的理由返回引用,并且应该首选按值返回。更好的封装、更松散的耦合、更少的错误机会等。但显然有理由通过引用返回。
  • @ChrisDrew 如果我们只讨论具有右值引用和移动语义的现代 C++,那么我同意你的看法。当然在耦合上,虽然我通常不隐藏任何内部类型。您建议哪种类型的对象最好默认返回副本?我使用的大部分代码都使用了很多内部句柄或容器和节点指针,所以我不喜欢默认复制。
  • 我会说,对于基本类型、小字符串、小对象,无论何时您可以获得返回值优化以及您可以移动任何时候,按值返回都是一个合理的默认选择。因此,大多数情况下,您希望避免将调用转发到中型到大型内部对象并愿意打破封装。这可能正是 OP 的情况。
  • 已编辑。经过进一步思考,我同意我的陈述过于强烈而无用。实际上,我认为没有任何默认方法,只有适当的方法。我使用大量胖对象或嵌套容器、DAG 和 AST,因此我主要使用指针和引用,但我的类可能是非典型的。
猜你喜欢
  • 2021-11-08
  • 1970-01-01
  • 2012-02-23
  • 2023-03-03
  • 1970-01-01
  • 1970-01-01
  • 2018-05-25
  • 2012-10-03
  • 2019-11-03
相关资源
最近更新 更多