工厂函数的最佳智能指针 return 类型是什么?
What is the best smart pointer return type for a factory function?
关于智能指针和新的 C++11/14 功能,我想知道具有这些功能的 return 值和函数参数类型的最佳实践是什么设施:
一个工厂函数(在 class 之外)创建对象并将它们 return 发送给 class 的用户。 (例如打开文档并 returning 一个可用于访问内容的对象。)
从工厂函数接受对象的实用函数,使用它们,但不取得所有权。 (例如计算文档中单词数的函数。)
函数在 return 之后保持对对象的引用(例如 UI 组件获取对象的副本,以便它可以在屏幕上绘制内容根据需要。)
工厂函数最好的 return 类型是什么?
- 如果它是一个原始指针,用户将不得不
delete
它正确,这是有问题的。
- 如果它 return 是
unique_ptr<>
那么用户就不能共享它。
- 如果它是
shared_ptr<>
那么我是否必须到处传递 shared_ptr<>
类型?这就是我现在正在做的事情,它导致了问题,因为我正在获取循环引用,防止对象被自动销毁。
效用函数的最佳参数类型是什么?
- 我想按引用传递将避免不必要地增加智能指针引用计数,但这有什么缺点吗?我想到的主要一个是它阻止我将派生的 classes 传递给采用 base-class 类型参数的函数。
- 有什么方法可以让调用者清楚地知道它不会复制对象吗? (理想情况下,如果函数体确实尝试复制对象,代码将不会编译。)
- 有没有办法让它独立于所使用的智能指针类型? (也许采用原始指针?)
- 是否可以有一个
const
参数来明确函数不会修改对象,而不破坏智能指针兼容性?
保持对对象的引用的函数的最佳参数类型是什么?
- 我猜
shared_ptr<>
是这里唯一的选择,这可能意味着工厂 class 也必须 return 一个 shared_ptr<>
,对吗?
下面是一些编译代码,希望能说明要点。
#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_ptr
或 weak_ptr
。 (无论哪种情况,它都可以引用基数 class,并添加 const
限定符,如果需要的话。)
What is the best parameter type for the function that keeps a reference to the object?
shared_ptr
或 unique_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_ptr
和 std::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_ptr
比 std::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
我认为这些指南非常有用。阅读引用的页面以获得更多背景和深入解释,这是值得的。
关于智能指针和新的 C++11/14 功能,我想知道具有这些功能的 return 值和函数参数类型的最佳实践是什么设施:
一个工厂函数(在 class 之外)创建对象并将它们 return 发送给 class 的用户。 (例如打开文档并 returning 一个可用于访问内容的对象。)
从工厂函数接受对象的实用函数,使用它们,但不取得所有权。 (例如计算文档中单词数的函数。)
函数在 return 之后保持对对象的引用(例如 UI 组件获取对象的副本,以便它可以在屏幕上绘制内容根据需要。)
工厂函数最好的 return 类型是什么?
- 如果它是一个原始指针,用户将不得不
delete
它正确,这是有问题的。 - 如果它 return 是
unique_ptr<>
那么用户就不能共享它。 - 如果它是
shared_ptr<>
那么我是否必须到处传递shared_ptr<>
类型?这就是我现在正在做的事情,它导致了问题,因为我正在获取循环引用,防止对象被自动销毁。
效用函数的最佳参数类型是什么?
- 我想按引用传递将避免不必要地增加智能指针引用计数,但这有什么缺点吗?我想到的主要一个是它阻止我将派生的 classes 传递给采用 base-class 类型参数的函数。
- 有什么方法可以让调用者清楚地知道它不会复制对象吗? (理想情况下,如果函数体确实尝试复制对象,代码将不会编译。)
- 有没有办法让它独立于所使用的智能指针类型? (也许采用原始指针?)
- 是否可以有一个
const
参数来明确函数不会修改对象,而不破坏智能指针兼容性?
保持对对象的引用的函数的最佳参数类型是什么?
- 我猜
shared_ptr<>
是这里唯一的选择,这可能意味着工厂 class 也必须 return 一个shared_ptr<>
,对吗?
下面是一些编译代码,希望能说明要点。
#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_ptr
或 weak_ptr
。 (无论哪种情况,它都可以引用基数 class,并添加 const
限定符,如果需要的话。)
What is the best parameter type for the function that keeps a reference to the object?
shared_ptr
或 unique_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_ptr
和 std::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_ptr
比 std::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 ashared_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, writeconst widget*
orconst 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
我认为这些指南非常有用。阅读引用的页面以获得更多背景和深入解释,这是值得的。