工厂函数的最佳智能指针 return 类型是什么?

What is the best smart pointer return type for a factory function?

关于智能指针和新的 C++11/14 功能,我想知道具有这些功能的 return 值和函数参数类型的最佳实践是什么设施:

  1. 一个工厂函数(在 class 之外)创建对象并将它们 return 发送给 class 的用户。 (例如打开文档并 returning 一个可用于访问内容的对象。)

  2. 从工厂函数接受对象的实用函数,使用它们,但不取得所有权。 (例如计算文档中单词数的函数。)

  3. 函数在 return 之后保持对对象的引用(例如 UI 组件获取对象的副本,以便它可以在屏幕上绘制内容根据需要。)

工厂函数最好的 return 类型是什么?

效用函数的最佳参数类型是什么?

保持对对象的引用的函数的最佳参数类型是什么?

下面是一些编译代码,希望能说明要点。

#include <iostream>
#include <memory>

struct Document {
    std::string content;
};

struct UI {
    std::shared_ptr<Document> doc;

    // This function is not copying the object, but holding a
    // reference to it to make sure it doesn't get destroyed.
    void setDocument(std::shared_ptr<Document> newDoc) {
        this->doc = newDoc;
    }
    void redraw() {
        // do something with this->doc
    }
};

// This function does not need to take a copy of the Document, so it
// should access it as efficiently as possible.  At the moment it
// creates a whole new shared_ptr object which I feel is inefficient,
// but passing by reference does not work.
// It should also take a const parameter as it isn't modifying the
// object.
int charCount(std::shared_ptr<Document> doc)
{
    // I realise this should be a member function inside Document, but
    // this is for illustrative purposes.
    return doc->content.length();
}

// This function is the same as charCount() but it does modify the
// object.
void appendText(std::shared_ptr<Document> doc)
{
    doc->content.append("hello");
    return;
}

// Create a derived type that the code above does not know about.
struct TextDocument: public Document {};

std::shared_ptr<TextDocument> createTextDocument()
{
    return std::shared_ptr<TextDocument>(new TextDocument());
}

int main(void)
{
    UI display;

    // Use the factory function to create an instance.  As a user of
    // this class I don't want to have to worry about deleting the
    // instance, but I don't really care what type it is, as long as
    // it doesn't stop me from using it the way I need to.
    auto doc = createTextDocument();

    // Share the instance with the UI, which takes a copy of it for
    // later use.
    display.setDocument(doc);

    // Use a free function which modifies the object.
    appendText(doc);

    // Use a free function which doesn't modify the object.
    std::cout << "Your document has " << charCount(doc)
        << " characters.\n";

    return 0;
}

在大多数情况下,我会 return 按值 unique_ptr。大多数资源不应该共享,因为这使得很难推断它们的生命周期。您通常可以通过避免共享所有权的方式来编写代码。在任何情况下,您都可以从 unique_ptr 中创建一个 shared_ptr,因此您不会限制您的选择。

What would the best return type be for the factory function?

unique_ptr 最好。它可以防止意外泄漏,并且用户可以从指针释放所有权,或者将所有权转移到 shared_ptr(为此目的有一个 constructor),如果他们想使用不同的所有权方案。

What is the best parameter type for the utility function?

引用,除非程序流程非常复杂以至于对象可能在函数调用期间被破坏,在这种情况下 shared_ptrweak_ptr。 (无论哪种情况,它都可以引用基数 class,并添加 const 限定符,如果需要的话。)

What is the best parameter type for the function that keeps a reference to the object?

shared_ptrunique_ptr,如果您希望它负责对象的生命周期而不用担心它。原始指针或引用,如果您可以(简单而可靠地)安排对象比使用它的所有对象都长寿。

我会以相反的顺序回答,所以从简单的案例开始。

Utility functions that accept objects from the factory functions, use them, but do not take ownership. (For example a function that counts the number of words in the document.)

如果您正在调用工厂函数,那么您始终会根据工厂函数的定义获取所创建对象的所有权。我想你的意思是一些 other 客户首先从工厂获得一个对象,然后希望将它传递给本身不拥有所有权的效用函数。

在这种情况下,效用函数根本不应该关心它所操作的对象的所有权是如何管理的。它应该简单地接受一个(可能 const)引用,或者——如果“没有对象”是一个有效条件——一个非拥有的原始指针。这将最大限度地减少接口之间的耦合并使实用程序功能最灵活。

Functions that keep a reference to the object after they return (like a UI component that takes a copy of the object so it can draw the content on the screen as needed.)

这些应该 std::shared_ptr by value。这从函数的签名中清楚地表明它们共享参数的所有权。

有时,拥有一个对其参数具有唯一所有权的函数也很有意义(想到构造函数)。那些应该采用 std::unique_ptr by value (或通过右值引用),这也将使语义从签名中清楚。

A factory function (outside of the class) that creates objects and returns them to users of the class. (For example opening a document and returning an object that can be used to access the content.)

这是一个困难的问题,因为 std::unique_ptrstd::shared_ptr 都有很好的论据。唯一清楚的是 return 拥有原始指针是不好的。

返回 std::unique_ptr 是轻量级的(与 returning 原始指针相比没有开销)并且传达了工厂函数的正确语义。调用该函数的人将获得对制造对象的独占所有权。如果需要,客户端可以以动态内存分配为代价从 std::unique_ptr 构建 std::shared_ptr

另一方面,如果客户端无论如何都需要 std::shared_ptr,让工厂使用 std::make_shared 以避免额外的动态内存分配会更有效。此外,在某些情况下,您必须简单地使用 std::shared_ptr,例如,如果托管对象的析构函数是非 virtual 并且智能指针要转换为指向基 class。但是 std::shared_ptrstd::unique_ptr 有更多的开销,所以如果后者足够,我们宁愿尽可能避免它。

所以总而言之,我想出了以下准则:

  • 如果您需要自定义删除器,return一个std::shared_ptr
  • 否则,如果您认为大多数客户无论如何都需要 std::shared_ptr,请利用 std::make_shared 的优化潜力。
  • 否则,return一个std::unique_ptr

当然,你可以通过提供两个工厂函数来避免这个问题,一个return是一个std::unique_ptr,另一个是return std::shared_ptr 这样每个客户都可以使用最适合其需求的内容。如果你经常需要这个,我想你可以通过一些聪明的模板元编程来抽象掉大部分冗余。

大多数其他答案都涵盖了这一点,但@T.C。链接到一些非常好的指导方针,我想在这里总结一下:

工厂函数

A factory that produces a reference type should return a unique_ptr by default, or a shared_ptr if ownership is to be shared with the factory. -- GotW #90

正如其他人指出的那样,作为 unique_ptr 的收件人,您可以根据需要将其转换为 shared_ptr

函数参数

Don’t pass a smart pointer as a function parameter unless you want to use or manipulate the smart pointer itself, such as to share or transfer ownership. Prefer passing objects by value, *, or &, not by smart pointer. -- GotW #91

这是因为当你通过智能指针时,你在函数开始时增加了引用计数器,并在结束时减少了它。这些是原子操作,需要跨多个 threads/processors 进行同步,因此在大量多线程代码中,速度损失可能非常高。

当你在函数中时,对象不会消失,因为调用者仍然持有对它的引用(和 can't do anything with the object until your function returns)所以如果你不打算增加引用计数是没有意义的在函数 returns.

之后保留对象的副本

对于不拥有对象的函数:

Use a * if you need to express null (no object), otherwise prefer to use a &; and if the object is input-only, write const widget* or const widget&. -- GotW #91

这不会强制您的调用者使用特定的智能指针类型 - 任何智能指针都可以转换为普通指针或引用。因此,如果您的函数不需要保留对象的副本或取得对象的所有权,请使用原始指针。如上所述,该对象不会在您的函数中间消失,因为调用者仍然持有它(除非在特殊情况下,如果这对您来说是个问题,您应该已经意识到了。)

对于取得对象所有权的函数

Express a “sink” function using a by-value unique_ptr parameter.

void f( unique_ptr<widget> );

-- GotW #91

这清楚地表明该函数取得了对象的所有权,并且可以将遗留代码中的原始指针传递给它。

对于共享对象所有权的函数

Express that a function will store and share ownership of a heap object using a by-value shared_ptr parameter. -- GotW #91

我认为这些指南非常有用。阅读引用的页面以获得更多背景和深入解释,这是值得的。