当函数采用shared_ptr(来自boost或C ++ 11 STL)时,是否要传递它:
我更喜欢第一种方法,因为我怀疑它会更快。 但这真的值得吗?还是有其他问题?
请问您提供选择的理由,或者,如果是这种情况,为什么您认为这没有关系。
-
问题是那些a折的东西。参考版本尖叫"我要为某些shared_ptr加上别名,如果需要,我可以更改它。",而值版本则显示"我要复制您的shared_ptr,因此尽管我可以更改它,但您永远不会知道。 )const-reference参数是真正的解决方案,它表示"我要为某些shared_ptr加上别名,我??保证不会对其进行更改。"(这与按值语义非常相似!)
-
嘿,我会对你们有关返回shared_ptr类成员的意见感兴趣。你通过const-refs做到吗?
-
第三种可能性是将std :: move()与C ++ 0x一起使用,这会交换shared_ptr
-
@Johannes:我将通过const-reference返回它,以避免任何复制/引用计数。再说一次,除非它们是原始的,否则我将通过const-reference返回所有成员。
-
C ++的可能重复项-指针传递问题
-
@Johannes:作为对Jalfs答案的评论,这是我的评论。我很惊讶您删除了答案。我不认为这真的错。我只是认为这个问题并不像乍看起来那样容易。我看到的问题代码是多线程实时3D场景渲染器。那不是大多数人依靠的由数据库支持的每天报表引擎。
-
通常性能瓶颈在其他地方,所以我不在乎。这几乎总是过早且无用的优化。我只是使用引用进行了一些实验,按值传递了shared_ptrs,移动了等等,却没有注意到实际应用程序(而不是基准测试)的性能差异。 C ++是一门学术语言,尽管根本没有关系,但总是有人告诉您应该做什么。
Scott,Andrei和Herb在C ++和Beyond 2011的"任何问题"会议上讨论并回答了这个问题。请从4:34观看shared_ptr性能和正确性。
不久,没有理由按值传递,除非目标是共享对象的所有权(例如,在不同的数据结构之间或在不同的线程之间)。
除非您可以按照上面链接的视频中Scott Meyers的说明进行移动优化,但这与您可以使用的C ++的实际版本有关。
在GoingNative 2012会议的"互动小组:任何问题!"中对该讨论进行了重大更新。值得一看,尤其是从22:50开始。
-
感谢您指出正确的位置以供参考。
-
但是如此处所示,按值传递更便宜:stackoverflow.com/a/12002668/128384也不应考虑在内(至少对于构造函数参数,例如,其中将shared_ptr成为该类成员的参数) )?
-
@stijn是和否。您指出的问题与解答是不完整的,除非它阐明了它所引用的C ++标准的版本。散布容易误导的一般永不/总是规则非常容易。除非读者花时间熟悉David Abrahams的文章和参考,否则考虑发布日期与当前C ++标准的关系。因此,给定发布时间,我的答案和您指出的答案都是正确的。
-
同意-我问的是因为这里的问题还很老,而您的nswer是最近的。也许您应该在答案中指出它更侧重于C ++ 11之前的版本?
-
@stijn Ive更新了答案。
-
"除非有多线程",否则MT绝非特别。
-
@curiousguy如果多个线程在副本上工作,是的,但是如果多个线程写入通过引用传递的可变shared_ptr,则不能保证线程安全。类似于此处讨论的内容stackoverflow.com/a/11492094/151641因此,您可能需要详细说明以避免模糊讨论。
-
@mloskot多个线程不应同时写入同一智能PTR。这不会改变以下事实:通过ref / value传递独立于MT。
-
我晚会晚了,但是我想按值传递shared_ptr的原因是它使代码更短,更漂亮。说真的Value*简短易读,但是很糟糕,所以现在我的代码中充满了const shared_ptr&,它的可读性大大降低,而且...更简洁。以前是void Function(Value* v1, Value* v2, Value* v3)的现在是void Function(const shared_ptr& v1, const shared_ptr& v2, const shared_ptr& v3),人们对此还可以吗?
-
const似乎是必经之路,而不仅仅是参考。如果需要修改shared_ptrs计数器,这将使意图更加清晰,并且使以后更难修改代码。
-
@Alex常见的做法是在课程之后立即创建别名(typedefs)。对于您的示例:class Value {...}; using ValuePtr = std::shared_ptr;然后您的功能变得更简单:void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3),您将获得最佳性能。那就是为什么您使用C ++,不是吗? :)
-
是的,我最近也开始这样做,我只是做Value_ptr而不是ValuePtr。
-
我不明白为什么如果您只打算共享所有权(例如,您打算将其分配给成员变量),则应该按值将shared_ptr传递给函数。这样做会导致ref计数器不必要的增加和减少,因为function参数被视为副本。还是我错过了什么?
-
等等,我只是再次阅读了这篇文章,我想我错过了说您随后在参数上使用std :: move转移所有权的部分。有一个想法是,如果我们按值接受shared_ptr,则可以通过复制构造函数或移动构造函数构造它,具体取决于我们传递的是l值还是r值。复制构造函数导致一个副本,而移动构造函数则不导致副本。因此,我们最多只能进行一次复制操作。如果我们通过引用接受了shared_ptr,那么我们总是会有一个复制操作?
-
通过查看Herb Sutter的这篇文章,这似乎已经过时了
-
@mbrt我认为Sutters GOTW文章与"没有理由通过价值传递"建议之间没有矛盾。
-
@mloskot我确实看到它:...除非您要使用/修改智能指针本身(如任何对象),否则不要通过智能指针传递。最主要的是,按值传递/ * /&仍然很好,应该仍然主要使用。只是我们现在有一些习惯用法来表达函数签名中的所有权转移,特别是按值传递unique_ptr表示"接收器",按值传递shared_ptr表示"将共享所有权"。因此,有理由这样做
-
@mbrt我理解您的评论,但这是shared_ptr用法的不同方面。在这里,我回答一个非常具体的问题:shared_ptr是通过值还是引用?我不将答案扩展到"如果完全通过shared_ptr"。
-
@mloskot,也许我不清楚。我指的是-按值传递shared_ptr意味着"将分享所有权"。这意味着有理由按值传递shared_ptr,这使语句"没有理由按值传递"无效。
-
@mbrt有一个"除非"子句。我已经说得更清楚了。
-
我仍然不理解"除非"子句:"除非目标是共享对象的所有权"-shared_ptr是否总是这样吗?同样,价值语义更"自然"。通过引用传递总是需要证明,而不是相反。为什么我们要通过引用?
-
@Alex这个问题非常具体:"何时函数应使用shared_ptr ..."。但是只有在通过在函数内部进行复制并在函数返回后存储供使用时共享指针所有权的情况下。如果该函数不需要所有权,则使用普通指针可以灵活地使用shared_ptr.get()或unique_ptr.get()进行调用。在我看来,采用shared_ptr或unique_ptr的函数意味着该函数需要所有权。
-
@einpoklum我同意,除非您需要共享所有权,否则使用shared_ptr是没有意义的-不管您如何传递它。至于按值传递更自然,那只是历史的偶然-现代代码出于效率的考虑,应该更喜欢按const引用传递,除非您传递非常简单的类型。
这是赫伯·萨特的
Guideline: 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.
Guideline: Express that a function will store and share ownership of a
heap object using a by-value shared_ptr parameter.
Guideline: Use a
non-const shared_ptr& parameter only to modify the shared_ptr. Use a
const shared_ptr& as a parameter only if you’re not sure whether or
not you’ll take a copy and share ownership; otherwise use widget*
instead (or if not nullable, a widget&).
-
感谢您与萨特的链接。这是一篇很棒的文章。我不同意他在widget *上的看法,如果C ++ 14可用,则更倾向于选用。 widget *与旧代码太不明确了。
-
+1包括将widget *和widget&包括在内。为了详细说明,当函数不检查/修改指针对象本身时,传递widget *或widget&可能是最佳选择。接口更通用,因为它不需要特定的指针类型,并且可以避免shared_ptr参考计数的性能问题。
-
由于第二条准则,我认为这应该是今天公认的答案。显然,它使当前接受的答案无效,即:没有理由通过价值来传递。
我个人将使用const引用。无需为了函数调用而再次递减参考计数。
-
我没有否决您的答案,但是在您优先考虑之前,要考虑的两种可能性各有利弊。知道和讨论这些利弊是一件好事。之后,每个人都可以自己做决定。
-
@Danvil:考虑到shared_ptr的工作方式,未通过引用的唯一可能的缺点是性能略有下降。这里有两个原因。 a)指针别名功能意味着要复制指向指针的数据和一个计数器(对于弱引用可能为2),因此,将数据四舍五入的开销会更高一些。 b)原子引用计数比普通的旧递增/递减代码稍慢,但是为了确保线程安全,原子引用计数是必需的。除此之外,两种方法在大多数意图和目的上都是相同的。
通过const引用,速度更快。如果您需要存储它,请在某个容器中添加参考。计数将通过复制操作自动增加。
-
请说出您为什么不赞成。
-
Downvote因为没有任何数字支持它的意见。
我运行以下代码,一次是使用foo将shared_ptr乘以const&,另一次是使用foo将shared_ptr乘以值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void foo(const std::shared_ptr<int>& p)
{
static int x = 0;
*p = ++x;
}
int main()
{
auto p = std::make_shared<int>();
auto start = clock();
for (int i = 0; i < 10000000; ++i)
{
foo(p);
}
std::cout <<"Took" << clock() - start <<" ms" << std::endl;
} |
在我的Intel Core 2 Quad(2.4GHz)处理器上使用VS2015,x86版本构建
1 2
| const shared_ptr& - 10ms
shared_ptr - 281ms |
按值复制版本要慢一个数量级。
如果要从当前线程同步调用函数,则最好使用const&版本。
-
您能否说说您使用了什么编译器,平台和优化设置?
-
我使用了vs2015的调试版本,更新了答案以立即使用发布版本。
-
我很好奇,如果启用优化功能,那么两者都能得到相同的结果
-
在2012年MacBook Pro(2.5 GHz Intel Core i7)上使用clang++ -O3 -std=c++11时,我分别得到42 ms和179227 ms。
-
优化没有太大帮助。问题是副本上引用计数上的锁争用。
-
那不是重点。这样的foo()函数甚至根本不应该接受共享指针,因为它不使用此对象:它应该接受int&并执行p = ++x;,并从main()调用foo(*p);。函数需要在需要执行某操作时接受一个智能指针对象,并且在大多数情况下,您需要将其移动(std::move())到其他位置,因此按值参数没有成本。
-
此结果可能是正确的,但这不是一个非常现实的应用程序。根据我的测试,这在现实世界的应用程序中根本不重要-除非使用数百万个shared_ptrs进行紧密循环这样的怪异操作,并且在此基准测试中没有其他事情。而且仍然是毫秒级的差异。在我的应用程序中,性能瓶颈在其他地方。
从C ++ 11开始,您应该比您想像的更多地重视const&的价值。
如果您使用的是std :: shared_ptr(而不是基础类型T),那么您这样做是因为您想对此做一些事情。
如果您想将其复制到某个地方,则更明智的做法是先进行复制,然后在内部进行std :: move,而不是通过const&进行复制,然后再进行复制。这是因为在调用函数时,允许调用者选择std :: move shared_ptr的选项,从而为自己节省了一组递增和递减操作。或不。也就是说,函数的调用者可以在调用函数之后并根据是否移动来决定是否需要std :: shared_ptr。如果您通过const&,这是无法实现的,因此最好按值取值。
当然,如果调用者都需要他的shared_ptr更长的时间(因此无法std :: move),并且您不想在函数中创建纯副本(例如,您想要一个弱指针,或者您有时只想复制它(取决于某些条件),那么const&可能仍然是首选。
例如,您应该
1
| void enqueue(std::shared< T > t) m_internal_queue.enqueue(std::move(t)); |
过度
1
| void enqueue(std::shared< T > const& t) m_internal_queue.enqueue(t); |
因为在这种情况下,您总是在内部创建副本
我不知道其中原子增量和减量所在的shared_copy复制操作的时间成本,我遭受了CPU使用率更高的问题。我从没想到原子的增加和减少会花费那么多的成本。
根据我的测试结果,int32原子增量和减量是非原子增量和减量的2或40倍。我在装有Windows 8.1的3GHz Core i7上获得它。前一个结果是在没有争用发生时出现的,后者是在发生争用的可能性很高时出现的。我要记住,原子操作最终是基于硬件的锁定。锁就是锁。发生争用时对性能不利。
遇到这种情况,我总是使用byref(const shared_ptr&)而不是byval(shared_ptr)。
shared_ptr不够大,它的构造函数/析构函数也做不到足够的工作,以使副本没有足够的开销来关心按引用传递与按副本传递的性能。
-
你测量了吗?
-
@stonemetal:创建新的shared_ptr期间原子指令如何处理?
-
它是非POD类型,因此在大多数ABI中,甚至"按值"传递它实际上也会传递一个指针。根本不是字节的实际复制,这就是问题所在。如您在asm输出中所见,按值传递shared_ptr会占用100多个x86指令(包括昂贵的lock指令,原子地增加/减少引用计数)。传递常量ref与传递指向任何对象的指针相同(在此示例中,在Godbolt编译器资源管理器上,尾部调用优化将其转换为简单的jmp而不是调用:godbolt.org/g/TazMBU)。
-
TL:DR:这是C ++,在其中复制构造函数可以完成的工作不仅仅是复制字节。这个答案就是垃圾。
-
stackoverflow.com/questions/3628081/shared-ptr-horrible-speed作为示例,通过值传递的共享指针与通过引用传递的共享指针的运行时差大约为33%。如果您正在处理对性能至关重要的代码,那么裸露的指针可以使您获得更大的性能提升。因此,如果您记得的话,一定要通过const ref传递,但是如果您不记得的话,这没什么大不了的。如果不需要,不要使用shared_ptr更为重要。