【问题标题】:Better to return a C++ reference or a weak_ptr?最好返回 C++ 引用或weak_ptr?
【发布时间】:2010-09-17 22:35:43
【问题描述】:

假设我有一个类,我希望用户能够引用我的一个成员。哪个更受欢迎?

class Member;

class ClassWithWeakPtr
{
private:
   boost::shared_ptr<Member> _member;
public:
   boost::weak_ptr<Member> GetMember();
};

class Member;

class ClassWithCppReference
{
private:
    Member _member;
public:
    Member& GetMember() {return _member;}
};

你怎么看?什么时候比另一个更好?

【问题讨论】:

    标签: c++ boost reference


    【解决方案1】:

    我想对 cme​​ts(主要来自 OP 和 Colomon)提出一些关于效率等的回应。就实际性能而言,很多时候复制东西并不重要。

    我编写了很多 defensive copying 的程序。这是 Java 中的一个习惯用法,因为所有对象都是通过指针传递的,所以会有很多别名发生,所以你复制任何进出你的类的东西来打破别名,确保你的类的客户不会违反你的类不变量通过事后修改对象。

    我编写的程序在某些地方防御性地复制了整个结构,包括整个列表和地图。为了确保性能不受影响,我对程序进行了广泛的分析。主要瓶颈在其他地方(即便如此,我调整了其他地方,以便剩下的主要瓶颈是网络)。这种防御性复制没有成为程序的热点。


    ETA:我觉得有必要澄清我的信息的重点,因为一位评论者对它的理解与我的意图不同,而且很可能其他人也这样做了。我的观点不是随意复制东西是可以的。而是应该始终分析他们的代码的性能,而不是疯狂地猜测它的性能。

    有时,当复制整个结构时仍能提供可接受的性能,同时使代码更易于编写,那么在我看来,与仅稍微快但更多的代码相比,这是一个更好的权衡(在大多数情况下)复杂。

    【讨论】:

    • 您能否通过分析器准确判断您是否系统地在整个代码中出现了一些低效率的问题?如果您的代码中的所有内容都运行得比应有的慢两倍,则分析器不会注意到任何事情,但您的程序仍然会很慢。
    • (当然,如果您处于解耦两个类很重要的情况下,强烈建议使用副本而不是非常量引用!)
    • I/O 等资源不受影响,所以是的,分析器仍然会找到您的热点,直到所有资源瓶颈都消失。没有多少应用程序是 ram 绑定的。
    • Java程序运行在虚拟机中,使用虚拟机管理的内存,是自优化的,垃圾回收的,与m向内核发出实际的内存分配请求完全不同。外推法在我看来相当大胆。
    • @Edouard:我的回答没有外推的意思。如果有的话,带回家的信息是应该衡量,而不是胡乱猜测。 (我实际上并没有说随意复制东西是可以的;即使在托管环境中也是如此。)
    【解决方案2】:

    在大多数情况下,我会提供

    const Member& GetMember() const;
    Member& GetMember(); // ideally, only if clients can modify it without
                         // breaking any invariants of the Class
    
    weak_ptr< Member > GetMemberWptr(); // only if there is a specific need
                                        // for holding a weak pointer.
    

    这背后的基本原理是(我也可能还有很多其他人)调用方法GetMember() 意味着Class 拥有member,因此返回的引用 仅在包含对象的生命周期内有效。

    在我遇到的大多数(但不是全部)代码中,GetMember() 方法通常用于立即处理返回的成员,而不是将其存储起来以供以后的用户使用。毕竟,如果您需要访问该成员,您可以随时从包含对象中请求它。

    【讨论】:

      【解决方案3】:

      为什么要返回一个弱指针?我认为您正在使它变得更加复杂而没有必要的好处。

      【讨论】:

        【解决方案4】:

        返回一个弱指针肯定会更昂贵并且没有任何实际用途 - 无论如何,您不能在主对象的生命周期内保持所有权。

        【讨论】:

          【解决方案5】:

          根据上下文,任何一个都可以。将“活动”链接返回给成员(如果您必须首先公开一个)的主要问题是,使用公开成员的人可能会比包含对象存在的时间更长。如果您的客户在其包含对象超出范围时通过引用访问所述成员,您将面临“奇怪的”崩溃、错误的值和类似的乐趣。不推荐。

          返回weak_ptr的主要优点是它能够向客户端传达他们正在尝试访问的对象已经消失,而引用无法做到这一点。

          我的 2 便士的价值是:

          • 如果您的客户都不会使用该成员,那么作为作者的您是唯一使用它并控制对象生命周期的人,返回一个引用就可以了。 const 引用会更好,但在现实世界中使用现有代码并不总是可行的。
          • 如果其他人会访问和使用该成员,尤其是在您编写库时,请返回weak_ptr。它会为您省去很多麻烦,并使调试更容易。
          • 我不会返回 shared_ptr。我见过这种方法,它通常受到那些对 weak_ptr/weak 参考概念感到不舒服的人的青睐。我看到的主要缺点是它会人为地将另一个对象成员的生命周期延长到其包含对象的范围之外。在 99% 的情况下,这在概念上是错误的,更糟糕​​的是,可能会将您的编程错误(访问不再存在的东西)变成概念错误(访问不应该存在的东西 )。

          【讨论】:

          • Err... 你不能将weak_ptr 提升为shared_ptr 吗?因此,只要您在原始 shared_ptr 消失之前得到它,您就可以人为地延长另一个对象成员的生命周期,即使该成员返回了一个 weak_ptr。
          • 是的,您可以(实际上必须这样做才能使用该对象),是的,您可以使用它来延长对象的生命周期是正确的。它只是让不小心射中你的脚变得更加困难,但它并不能保护你免受意图......
          • 因此,对于weak_ptr,当用户将weak_ptr 提升为shared_ptr 时,引用的范围“仅”错误地扩展。如果延长对象的持续时间是错误的,那么稍微延长它也可能是错误的,现在你有一个占用空间更小的错误。那不是更好......
          【解决方案6】:

          我认为唯一合理的答案是,这取决于 Member 与 Class 的关系,以及您希望 Class 的用户能够做什么。 _member 是否具有独立于 Class 对象的有意义的存在?如果没有,那么我认为使用shared_ptr 没有任何意义,无论您返回weak_ptr 还是shared_ptr。本质上,要么是让用户访问一个成员对象,这个成员对象的寿命可能比赋予它意义的类对象寿命长。这可能会防止崩溃,但代价是隐藏严重的设计错误。

          正如 awgn 所指出的,你应该非常小心地暴露你的类内部。但我认为肯定有它的位置。例如,我的代码中有一个类,它代表一个文件对象,由一个文件头对象和一个文件数据对象组成。完全复制文件类中的头接口会很愚蠢并且违反 DRY。我想,您可以强制用户获取标头对象的副本,对副本进行更改,然后将外部对象复制回整个文件类。但这会带来很多低效率,这只会让您有能力使标头的文件对象表示与标头对象表示不同。如果您确定这不是您想要做的事情,您不妨返回一个对标头的非常量引用——它更简单、更高效。

          【讨论】:

            【解决方案7】:

            您应该避免泄露您的内部信息;这是指导方针。 42 个“C++ 编码标准”(Herb Sutter 和 Andrei Alexandrescu)。如果由于某些原因必须这样做,最好返回 const 引用 而不是指针,因为 constness 不会通过它传播。

            weak_ptr 似乎是一个可行的解决方案,即使它的基本目的是避免 shared_ptr 的循环。相反,如果你返回一个 shared_ptr,你会延长这种内部的寿命,这在大多数情况下是没有意义的。

            当有人处理对其内部的引用时实例消失的问题应该是线程之间的正确同步/通信。

            【讨论】:

            • 同步不一定能解决返回引用的问题。如果 GetMember() 被临时调用,引用几乎会立即失效。
            • 我同意你的看法。我发帖时很懒惰,没有添加 const-ness。
            【解决方案8】:

            为什么不返回shared_ptr&lt;&gt;?这样,客户端可以在需要时使用返回的内容,但如果“服务器”类消失也没有问题。

            weak_ptr&lt;&gt; 的语义有很多意义的情况并不多(缓存和???)。通常,当客户要求某物时,它希望拥有由客户自己决定的该物的所有权(共享所有权与完全所有权一样好)。

            如果您遇到“服务器”对象可以在不知道客户端的情况下被销毁的情况(您可能想要use weak_ptr&lt;&gt;shared_ptr&lt;&gt;),那么您可以做的最糟糕的事情就是返回对成员。在这种情况下,客户端无法知道访问返回的引用是否安全。您必须返回成员的副本或可以正确管理所返回成员的生命周期的智能指针。

            请记住,如果有一个表达式产生一个临时的ClassWithCppReference(这并不总是很明显),调用GetMember() 的客户端甚至无法在下一条语句中使用返回的引用。

            【讨论】:

              【解决方案9】:

              我的基本,无知的指导方针:

              我意识到,如果 ClassWithCppReference 的实例可能会消失,而某人仍然拥有对成员的引用,则后者可能是不安全的。但是,我也可以看到具有 POD 类型的后一个类的参数,例如向设备发送消息,并且您不想一直复制成员。

              【讨论】:

                猜你喜欢
                • 2013-04-30
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-12-03
                • 2017-10-02
                • 2020-03-13
                • 2020-08-26
                • 2016-05-23
                相关资源
                最近更新 更多