【问题标题】:Should accessors return values or constant references?访问器应该返回值还是常量引用?
【发布时间】:2011-01-06 10:14:45
【问题描述】:

假设我有一个类 Foo 和一个 std::string 成员 strget_str 应该返回什么?

std::string Foo::get_str() const
{
    return str;
}

const std::string& Foo::get_str() const
{
    return str;
}

在 C++ 中什么更惯用?

【问题讨论】:

标签: c++ reference return-value accessor


【解决方案1】:

简短的回答是:这取决于:-)

从性能的角度来看,返回引用(通常)更好:您保存了新 std::string 对象的创建。 (在这种情况下,创建成本足够高,并且对象的大小足够高以 justify 做出这个选择至少值得考虑 - 但情况并非总是如此。使用更小的或内置的 -在类型上,性能差异可能可以忽略不计,或者按价值返回甚至可能更便宜)。

从安全的角度来看,返回原始值的副本可能会更好,因为恶意客户端可以丢弃常量。如果该方法是公共 API 的一部分,则尤其要考虑这一点,即您(r 团队)无法完全控制返回值的(错误)使用方式。

【讨论】:

  • +1,这是一个很好的观点,先生。事实上,API 代码不应该暴露私有成员,即使是间接暴露。
  • 一张std::string有多长?在许多实现中,复制一个简短的 std::string 很便宜(取决于您衡量它的对象)。
  • @Charles,我似乎记得在 Visual C++ 平台上,空 string 的大小为 20 字节,但当然它取决于实现。我并不是说 std::string& 的性能优势总是很明确,但我的措辞不清楚 - 现在我澄清了。
  • 您基本上必须相信您的 API 的用户不会绕过 const 修饰符。他们是在自取其辱,如果代码不起作用,那是他们自己的错。也就是说,我不认为 隐藏数据 是不返回 const 的好理由。
  • @edA-qa mort-ora-y,有时信任您的客户是一种选择,有时则不是。了解自己的(缺乏)选择是件好事。
【解决方案2】:

拥有访问器方法的目标之一是尝试,至少在某种程度上,将您的类实现从其接口抽象出来。

按值返回更好,因为引用的对象没有生命周期问题。如果您决定不使用std::string 成员,而是使用std::stringstream,或者即时创建std::string,则无需更改界面。

通过 const 引用返回与通过 const 引用获取参数并不相反,通过 const 引用获取值不会将您的内部数据表示绑定到外部接口。

【讨论】:

  • 没有什么可以阻止您拥有额外的std::string 成员,严格用于从函数返回的目的。只需在函数中复制到它并返回即可。
  • @edA-qa mort-ora-y:确实如此,但如果这是您拥有这样一个成员的唯一原因,这意味着维护(代码复杂性)和运行时大小开销。
  • 我同意。我只是想指出,使用const & 并不会阻止向后兼容性(即使内部结构发生变化,您也不需要更改接口)。
【解决方案3】:

一般来说(除非存在已证实的性能问题)我会按价值返回。

首先存在语义差异,如果您的属性发生更改,您希望您的客户在调用函数时更新更改还是获取值?

存在明显的正确性问题,如果您通过引用返回,调用函数的实体可能会保留该引用,并可能在您的对象被破坏后使用它(这不太好)。

另一个问题是多线程代码,如果一个线程在您更新变量时从 const 引用中读取会带来很多麻烦。

无论如何,我认为最常见的用例是函数的调用者将值存储在变量中。

string val = obj->get_str();
// use val now

如果这是真的(相对于没有变量的cout << obj->get_str()),即使您通过引用返回,您也总是必须为val 构造一个新字符串,因为编译器可以执行RVO 按值版本不会低于 by-const-ref 变体。


结论:如果您知道这是性能问题并且确定返回值的存储时间不会超过您的object 将存在并且你不希望被不同的线程使用,那么通过 const 引用返回就可以了。

【讨论】:

  • 虽然我不同意默认返回值,但你关于多线程的观点很重要。如果对象是线程安全的,则不应返回对可以在另一个线程中更改的数据的引用。 当然不包括一些外部锁定机制
【解决方案4】:

按值返回意味着您不必将内部 std::string 存储在要返回的类的某个位置。

在纯虚方法中,最好不要假设 std::string 会在那里,因此按值返回 std::string。

在一个明确存在 std::string 成员的具体类中,您将返回对它的引用,为了提高效率,您可以通过 const 引用返回它。即使您以后必须更改它,您也不需要更改使用该类的功能。

在内部字符串可能在调用之间发生变化的多线程模型中,当然,您可能需要按值返回(假设该类的用户将在调用时获得字符串值的“快照”视图)调用完成)。

通过引用返回通常更有效。但是,我确实有一个不可变的引用计数字符串类,您可以有效地按值返回,而且我曾经经常使用它。

顺便说一句,有些人会建议通过 const 值返回 std::string。我认为这不是最好的方法,因为它会阻止用户将其“交换”到局部变量中。

【讨论】:

    【解决方案5】:

    AFAIK,该规则与在决定是通过值还是常量引用获取函数参数时使用的规则相同。如果 sizeof 返回的值足够小,那么我倾向于使用返回一个副本,否则返回一个 const 引用。

    【讨论】:

    • sizeof(std::string) 足够小,但复制效率取决于字符串的大小
    • 同样的问题不影响将参数作为返回值。返回引用需要调用实体在函数返回后实际维护对象。获取const 引用(通常)只需要调用者将对象维护到一个明确定义的点:函数的返回。
    【解决方案6】:

    我相信第二个实现(常量引用)是正确的:

    1. 返回的对象是不可变的,因此遵守封装规则。
    2. 效率稍高一些,因为没有 str 的复制。

    但是,第一种方法几乎同样有效。

    【讨论】:

      【解决方案7】:

      通常,您应该按值(例如,int、short、char、long 等)返回 POD,并为更复杂的类型返回 const 引用:

      int getData() const;
      short getMoreData() const;
      const std::string& getName() const;
      const ComplexObject& getComplexData() const;
      

      【讨论】:

        【解决方案8】:

        这取决于你想对返回值做什么。

        如果您只想进行查询而不修改str,这会更好。

        const std::string& Foo::get_str() const
        {
            return str;
        }
        

        否则,就这样吧:

        std::string& Foo::get_str()
        {
            return str;
        }
        

        如果你想要str复制/克隆,那么使用这个:

        std::string Foo::get_str() const
        {
            return str;
        }
        

        【讨论】:

        • 这看起来是世界上最糟糕的,不是吗?让客户直接访问对象的私有数据。在第二种情况下,我肯定会投票支持按价值返回
        • 第二种情况是通过 const-reference 或 value 返回的不同场景,它们在很多时候都是等价的。
        • std::string& Foo::get_str() - 通常不这样做。您想修改班级中的成员,而不是修改其他任何人。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-25
        • 1970-01-01
        • 1970-01-01
        • 2011-08-01
        • 2011-04-27
        • 1970-01-01
        相关资源
        最近更新 更多