【问题标题】:Is it alright to return a reference to a non-pointer member variable as a pointer?可以将非指针成员变量的引用作为指针返回吗?
【发布时间】:2012-09-07 10:19:41
【问题描述】:

我最近遇到了一些看起来像这样的 C++ 代码:

class SomeObject
{
private:
    // NOT a pointer
    BigObject foobar;

public:
    BigObject * getFoobar() const
    {
        return &foobar;
    }
};

我问程序员为什么不把 foobar 做成一个指针,他说这样他就不用担心分配/释放内存了。我问他是否考虑使用一些智能指针,他说这也很有效。

这是不好的做法吗?看起来很hackish。

【问题讨论】:

  • 我没有发现任何问题。我不会真的这样做,但我仍然没有看到任何真正的问题。
  • 表达式&foobar不是引用,是成员地址
  • 指针通常不好。在这种情况下,代码甚至无法编译,但返回一个 const 引用是完全合适的。
  • 你不能“只是”让它成为一个指针;您还必须确保它指向某个对象,并管理该对象的生命周期。
  • @BЈовић:在这个简单的例子中,你是对的;但通常您只想授予只读访问权限,或者在访问时检查和维护不变量,在这种情况下,您确实希望它是私有的。

标签: c++ pointers memory-management reference smart-pointers


【解决方案1】:

这是完全合理的,绝不是“hackish”;尽管返回一个引用来表明该对象确实存在可能会被认为更好。指针可能为空,并可能导致一些人认为他们应该在使用后删除它。

对象必须存在于某处,并且作为对象的成员存在通常与存在于其他任何地方一样好。通过将其与拥有它的对象分开动态分配来添加额外的间接级别会降低代码效率,并增加确保正确解除分配的负担。

当然,如果成员函数返回非const 引用或指向成员的指针,则成员函数不能是const。这是使其成为成员的另一个优点:SomeObject 上的 const 限定符也适用于其成员,但不适用于它仅具有指针的任何对象。

唯一的危险是对象可能会在有人仍然拥有指向它的指针或引用时被销毁;但是,无论您如何管理,这种危险仍然存在。如果对象生命周期太复杂而无法以其他方式管理,则智能指针可以提供帮助。

【讨论】:

    【解决方案2】:

    您返回的是指向成员变量的指针,而不是引用。这是一个糟糕的设计。 您的类管理 foobar 对象的生命周期,并通过返回指向其成员的指针,您可以使您的类的使用者在 SomeObject 对象的生命周期之后继续使用该指针。并且它还使用户可以根据需要更改 SomeObject 对象的状态。

    相反,您应该重构您的类,以包含将在 SomeObject 类中的 foobar 上执行的操作作为方法。

    ps。考虑正确命名你的类。当你定义它是一个类。当您实例化时,您就有了该类的一个对象。

    【讨论】:

    • +1 用于指出错误的命名。无论如何,C++ 都很难,而且更容易被奇怪的名字混淆。
    • 我确定名称BigObjectSomeObjectfoobar 只是构成示例的占位符。
    • SomeObject 是一个占位符类名,就像Object 是一个真正的类名一样合理。我可以提到的某些语言确实使用 Object 作为类名,因此您应该与 James Gosling 和 Anders Hejlsberg 一起讨论这个问题 :-)
    • @Martinho:因此“考虑”部分,我不提倡它;命名时要注意的事项。
    • 我认为 C++ 中类和对象之间的区别与 Java 或 C# 中的相同区别没有显着差异。没有 std::object 的原因不是因为 C++ 的特定命名约定,而是因为根本没有最终的超类。我也不认为在它们代表的对象之后命名类会模糊类/实例的区别。 vector 类型的对象“是一个向量”。 SomeObject 类型的对象“是某个对象”。 Class可以出现在像MyClass这样的占位符类名中,但我认为说它必须是错误的。
    【解决方案3】:

    通常认为返回指向内部数据的指针并不理想;它阻止类管理对其自身数据的访问。但是,如果您无论如何都想这样做,我认为这里没有什么大问题;它简化了内存的管理。

    【讨论】:

      【解决方案4】:

      这是不好的做法吗?看起来很hackish。

      是的。如果类在指针之前超出范围,则成员变量将不再存在,但指向它的指针仍然存在。任何取消引用该指针后类销毁的尝试都将导致未定义的行为 - 这可能导致崩溃,或者可能导致难以找到读取任意内存并将其视为 BigObject 的错误。

      如果他考虑使用一些智能指针

      使用智能指针,特别是 std::shared_ptr<T> 或 boost 版本,在技术上可以在这里工作并避免潜在的崩溃(如果您通过共享指针构造函数进行分配) - 但是,它也会混淆谁拥有该指针 - 类,或者呼叫者,召集者?此外,我不确定您是否可以将指向对象的指针添加到智能指针。

      这两点都涉及从类中获取指针的技术问题,但真正的问题应该是“为什么?”比如“你为什么要从一个类中返回一个指针?”在某些情况下,这是唯一的方法,但通常您不需要返回指针。例如,假设需要将变量传递给 C API,该 API 接受指向该类型的指针。在这种情况下,最好将 C 调用封装在类中。

      【讨论】:

      • “任何在类销毁后取消引用该指针的尝试都将导致分段错误” - 不太可能,因为释放内存很少会导致物理上无法访问。你可能会比段错误更糟——代码会继续运行,但会做错事。
      • @Steve 好吧,这也可能发生......我只是想避免“继续跑步”作为解释,以防 OP 没有读到这三个词之外的任何内容。
      • 我个人会说,“有未定义的行为”,并提出“那是什么?”的问题。当它出现时 ;-) 我不想给人们一种错误的印象,即当他们犯这个错误时,他们会得到一个段错误,因为如果他们有这种印象,那么当他们没有 得到一个段错误,他们会得出结论,他们没有犯了这个错误。
      【解决方案5】:

      只要调用者知道getFoobar()返回的指针在SomeObject对象析构时失效,就可以了。此类附带条件和警告在较旧的 C++ 程序和框架中很常见。

      由于历史原因,即使是当前的图书馆也必须这样做。例如std::string::c_str,它返回一个指向字符串内部缓冲区的指针,当字符串被破坏时,该缓冲区变得不可用。

      当然,这在大型或复杂的程序中很难保证。在现代 C++ 中,首选的方法是尽可能为所有内容提供简单的“值语义”,以便每个对象的生命周期都由使用它的代码控制。因此没有裸指针,没有显式的newdelete 调用散布在您的代码中等,因此无需要求程序员手动确保他们遵守规则。

      (然后,在您完全无法避免对对象生命周期的共同责任的情况下,您可以使用智能指针。)

      【讨论】:

      • 我有点同意,但是对于std::string::c_strstd::string::operator[] 之类的需求,首选的现代 C++ 解决方案是什么? getter 和 setter 代替引用?将weak_ptr 保存到其父容器的内部数据结构的迭代器,以便他们可以安全地检查生命周期?将shared_ptr 返回到代表容器的第 n 个元素的某个节点,因此如果仍在使用该元素可以比容器寿命更长?也许我只是老了,我不太明白如何完全避免引用语义;-)
      • 我也老了...我同意在 C++ 中这只是一个有争议的问题。如果您在平面地址空间中编写系统软件,则需要 C++ 能够直接与 C API 对话并处理内存。如果您正在编写更高级的应用程序代码,那么您就不需要任何这些工具,而且没有它们您会过得更好……那您为什么要使用 C++? :)
      【解决方案6】:

      这里有两个不相关的问题:

      1) 您希望您的SomeObject 实例如何管理它需要的BigObject 实例?如果SomeObject 的每个实例都需要自己的BigObject,那么BigObject 数据成员是完全合理的。在某些情况下,您想要做一些不同的事情,但除非出现这种情况,否则请坚持使用简单的解决方案。

      2) 你想让SomeObject 的用户直接访问它的BigObject 吗?默认情况下,这里的答案是“否”,基于良好的封装。但是,如果您确实愿意,那么这不会改变对 (1) 的评估。此外,如果您确实想这样做,您不一定需要通过指针来这样做——它可以是通过引用甚至是公共数据成员。

      第三个可能的问题可能会改变对 (1) 的评估:

      3) 您是否要让SomeObject 的用户直接访问BigObject 的实例,他们会在他们从中获得它的SomeObject 实例的生命周期之后继续使用该实例?如果是这样,那么数据成员当然不好。正确的解决方案可能是shared_ptr,或者让SomeObject::getFooBar 成为每次调用时返回不同BigObject 的工厂。

      总结:

      • 除了它不能编译(getFooBar() 需要返回const BigObject*)之外,到目前为止没有理由认为这段代码是错误的。可能会出现其他导致错误的问题。
      • 返回const & 可能比const * 更好。您返回的内容与 foobar 是否应该是 BigObject 数据成员无关。
      • foobar 设为指针或智能指针当然不是“仅仅”——其中任何一个都需要额外的代码来创建BigObject 的实例来指向。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-21
        • 2014-07-25
        • 1970-01-01
        • 2016-11-27
        • 1970-01-01
        • 2015-06-12
        相关资源
        最近更新 更多