13

假设我有一个类,我希望用户能够引用我的一个成员。哪个是首选?

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;}
};

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

4

9 回答 9

14

为什么不返回一个shared_ptr<>?这样,只要客户需要,客户就可以使用返回的内容,但是如果“服务器”类消失了也没有问题。

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

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

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

于 2008-10-18T18:57:48.257 回答
4

我想提出一些东西来回应关于效率等的评论(主要来自 OP 和科洛蒙)。就实际性能而言,很多时候复制东西真的无关紧要。

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

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


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

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

于 2008-10-18T21:07:26.997 回答
4

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

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

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

于 2008-10-18T19:14:56.383 回答
2

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

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

于 2008-10-18T20:52:32.567 回答
1

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

于 2009-03-22T00:04:39.060 回答
1

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

于 2009-03-22T00:06:37.497 回答
0

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

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

于 2008-10-18T18:40:19.293 回答
0

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

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()方法通常用于立即对返回的成员进行处理,而不是将其存储起来以供以后用户使用。毕竟,如果您需要访问该成员,您可以随时从包含对象请求它。

于 2009-07-22T11:53:50.410 回答
-1

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

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

我的 2 便士的价值是:

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