将unique_ptr作为裸指针返回以表示所有权语义是一种不好的做法吗?

49

我编写了一个静态工厂方法,该方法返回一个新的Foobar对象,并从另一个数据对象中填充。最近,我对所有权语义变得着迷,想知道通过使这个工厂方法返回一个 unique_ptr 是否传达了正确的信息。

class Foobar {
public:
    static unique_ptr<Foobar> factory(DataObject data);
}

我的意图是告诉客户端代码他们拥有指针。如果没有智能指针,我会简单地返回 Foobar*。然而,为了避免潜在的错误,我希望强制删除这个内存,所以 unique_ptr 似乎是一个合适的解决方案。如果客户端想要延长指针的生命周期,他们只需要在获取 unique_ptr 后调用 .release()

Foobar* myFoo = Foobar::factory(data).release();

我的问题分为两个部分:

  1. 这种方法是否表达了正确的所有权语义?
  2. 返回 unique_ptr 而不是裸指针是一种“不良做法”吗?

你要返回一个unique_ptr告诉客户端他们拥有指针吗?这正好与我的预期相反(因为他们必须显式地接管unique_ptr的所有权)。 - jeffknupp
2
你可能想使用移动语义(如果你能使用C++11的话)。这样,用户可以决定如何延长工厂创建的对象的生命周期。 - evnu
1
@evnu 那是自动完成的,不是吗? - Seth Carnegie
你如何处理一个在正常操作中可能会因为移动返回而失败的工厂?(比如,一个解析器可能无法匹配特定的模式,表明你应该尝试另一个模式)。对于这种情况,unique_ptr似乎是理想的选择,因为调用者可以测试它是否成功匹配,即使成功了,也可以决定是否显式地将指针移动到一个存活的对象或让它隐式地死亡,因为它离开了自己的作用域。 - matthias
您可以在这里查看unique_ptr的典型用法(尤其是关于所有权语义)。该文章描述了工厂模式以及将unique_ptr传递给函数。 - TiMoch
显示剩余4条评论
3个回答

59

从工厂方法返回std::unique_ptr很好,应该被推荐使用。它所传递的信息是(我个人认为):您现在是该对象的唯一所有者。此外,为了方便您,该对象知道如何销毁自己。

我认为这比返回裸指针要好得多(客户端必须记住如何以及是否处理该指针)。

然而,我不理解您关于释放指针以延长其生命周期的评论。通常情况下,我很少看到有理由在智能指针上调用release,因为我认为指针应该始终由某种RAII结构管理(我调用release的唯一情况是在做了某些事情并需要进行额外清理之后,将指针放入另一个管理数据结构中,例如具有不同删除器的unique_ptr)。

因此,客户端可以(并且应该)简单地将unique_ptr存储在某个地方(例如另一个已从返回值移动构造的unique_ptr),只要他们需要该对象就可以了(或者如果他们需要指针的多个副本,则使用shared_ptr)。因此,客户端代码应该如下所示:

std::unique_ptr<FooBar> myFoo = Foobar::factory(data);
//or:
std::shared_ptr<FooBar> myFoo = Foobar::factory(data);

个人建议你也在工厂对象中添加一个typedef,用于返回指针类型(在本例中为std::unique_ptr<Foobar>),或者使用的删除器(在本例中为std::default_deleter)。这样如果以后决定更改指针的分配方式(因此需要不同的指针销毁方法,这将作为std::unique_ptr的第二个模板参数可见),就会更加容易。

所以我会做如下更改:

class Foobar {
public:  
    typedef std::default_deleter<Foobar>     deleter;
    typedef std::unique_ptr<Foobar, deleter> unique_ptr;

    static unique_ptr factory(DataObject data);
}

Foobar::unique_ptr myFoo = Foobar::factory(data);
//or:
std::shared_ptr<Foobar> myFoo = Foobar::factory(data);

3
抱歉我的release()提及可能会造成误导或混淆。这段新代码将被加入到一个相当庞大的现有应用程序中(1-2百万行C ++代码),该应用程序不使用任何智能指针(除了COM)。如果旧代码使得必须访问原始指针,我知道我的团队中的其他人会感到担忧。当然,我会给出适当的警告,并建议尽可能使用unique_ptr/shared_ptr。我很喜欢您的typedef想法,这使得您的答案与James McNellis的答案不同。感谢您的洞见。 - Bret Kuhns

17

std::unique_ptr是唯一拥有其指向对象的智能指针。它表示“我拥有这个对象,没有其他人拥有它。”

这正是您试图表达的:您正在说“调用此函数的人:您现在是这个对象的唯一所有者;请随意处理,它的生命周期由您负责。”


确实,但为什么不让它返回一个std::shared_pointer<>呢?它可以达到相同的效果,但允许多个指针副本。 - André Caron
8
如果您只想将所有权传递给调用方,为什么要强制使用std::shared_ptr呢?如果调用方想要,让它将对象的所有权交给std::shared_ptr(或者如果调用方想要使用其他种类的智能指针)。 - James McNellis
7
@André Caron:原因基本上是性能。工厂无法知道客户是否需要std::shared_ptr相对于std::unique_ptr所添加的功能,因此为了一些可能不必要的功能而牺牲性能是不值得的。由于shared_ptr可以从unique_ptr构造和分配,因此实际上没有任何好处。 - Grizzly
调用者并不严格拥有指针。调用者的 unique_ptr 对象拥有该指针。 :p - wilhelmtell
1
@AndréCaron 一个 shared_ptr 可以从 unique_ptr 构造,但反过来通常不可能。 - bames53
@bames53:啊!那确实是一个很好的理由。我认为Dietmar Kuhl的回答最清晰地表达了这个想法。谢谢! - André Caron

6

它确切地传达了正确的语义,这是我认为所有C++工厂应该遵循的方式:std::unique_ptr<T>不强制任何形式的所有权语义,并且它非常便宜。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接