C++ - 传递对 std::shared_ptr 或 boost::shared_ptr 的引用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/327573/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:40:28  来源:igfitidea点击:

C++ - passing references to std::shared_ptr or boost::shared_ptr

c++boostpass-by-referencepass-by-valueshared-ptr

提问by abigagli

If I have a function that needs to work with a shared_ptr, wouldn't it be more efficient to pass it a reference to it (so to avoid copying the shared_ptrobject)? What are the possible bad side effects? I envision two possible cases:

如果我有一个需要使用 a 的函数shared_ptr,将它的引用传递给它会不会更有效(以避免复制shared_ptr对象)?可能的不良副作用是什么?我设想了两种可能的情况:

1) inside the function a copy is made of the argument, like in

1) 在函数内部,参数由一个副本组成,例如

ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)  
{  
     ...  
     m_sp_member=sp; //This will copy the object, incrementing refcount  
     ...  
}  

2) inside the function the argument is only used, like in

2)在函数内部只使用参数,如

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}  

I can't see in both cases a good reason to pass the boost::shared_ptr<foo>by value instead of by reference. Passing by value would only "temporarily" increment the reference count due to the copying, and then decrement it when exiting the function scope. Am I overlooking something?

在这两种情况下,我都看不到传递boost::shared_ptr<foo>按值而不是按引用的充分理由。由于复制,按值传递只会“暂时”增加引用计数,然后在退出函数作用域时减少它。我是否忽略了什么?

Just to clarify, after reading several answers: I perfectly agree on the premature-optimization concerns, and I always try to first-profile-then-work-on-the-hotspots. My question was more from a purely technical code-point-of-view, if you know what I mean.

只是为了澄清,在阅读了几个答案之后:我完全同意过早优化问题,并且我总是尝试首先分析然后在热点上工作。如果您知道我的意思,我的问题更多是从纯粹的技术代码角度出发。

采纳答案by Daniel Earwicker

The point of a distinct shared_ptrinstance is to guarantee (as far as possible) that as long as this shared_ptris in scope, the object it points to will still exist, because its reference count will be at least 1.

不同shared_ptr实例的要点是(尽可能)保证只要 thisshared_ptr在范围内,它指向的对象仍然存在,因为它的引用计数至少为 1。

Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
    // sp points to an object that cannot be destroyed during this function
}

So by using a reference to a shared_ptr, you disable that guarantee. So in your second case:

因此,通过使用对 a 的引用shared_ptr,您可以禁用该保证。所以在你的第二种情况下:

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}

How do you know that sp->do_something()will not blow up due to a null pointer?

你怎么知道它sp->do_something()不会因为空指针而爆炸?

It all depends what is in those '...' sections of the code. What if you call something during the first '...' that has the side-effect (somewhere in another part of the code) of clearing a shared_ptrto that same object? And what if it happens to be the only remaining distinct shared_ptrto that object? Bye bye object, just where you're about to try and use it.

这完全取决于代码的那些“...”部分中的内容。如果您在第一个 '...' 期间调用具有副作用(在代码的另一部分的某处)清除shared_ptr同一个对象的东西怎么办?如果它恰好是该shared_ptr对象唯一剩余的不同点怎么办?再见对象,就在您将要尝试和使用它的地方。

So there are two ways to answer that question:

所以有两种方法可以回答这个问题:

  1. Examine the source of your entire program very carefully until you are sure the object won't die during the function body.

  2. Change the parameter back to be a distinct object instead of a reference.

  1. 非常仔细地检查整个程序的源代码,直到您确定对象不会在函数体执行期间死亡。

  2. 将参数改回不同的对象而不是引用。

General bit of advice that applies here: don't bother making risky changes to your code for the sake of performance until you've timed your product in a realistic situation in a profiler and conclusively measured that the change you want to make will make a significant difference to performance.

适用于此处的一般建议:不要为了性能而费心对代码进行有风险的更改,直到您在分析器中对产品的实际情况进行计时,并最终测量出您想要进行的更改将产生性能的显着差异。

Update for commenter JQ

评论者 JQ 的更新

Here's a contrived example. It's deliberately simple, so the mistake will be obvious. In real examples, the mistake is not so obvious because it is hidden in layers of real detail.

这是一个人为的例子。故意简单,所以错误会很明显。在真实的例子中,错误并不那么明显,因为它隐藏在真实细节的层次中。

We have a function that will send a message somewhere. It may be a large message so rather than using a std::stringthat likely gets copied as it is passed around to multiple places, we use a shared_ptrto a string:

我们有一个函数可以在某处发送消息。它可能是一个大消息,因此std::string我们使用 ashared_ptr到一个字符串,而不是使用在它被传递到多个地方时可能被复制的a :

void send_message(std::shared_ptr<std::string> msg)
{
    std::cout << (*msg.get()) << std::endl;
}

(We just "send" it to the console for this example).

(对于这个例子,我们只是将它“发送”到控制台)。

Now we want to add a facility to remember the previous message. We want the following behaviour: a variable must exist that contains the most recently sent message, but while a message is currently being sent then there must be no previous message (the variable should be reset before sending). So we declare the new variable:

现在我们要添加一个工具来记住之前的消息。我们想要以下行为:必须存在一个包含最近发送的消息的变量,但是当当前正在发送消息时,则必须没有先前的消息(该变量应该在发送前重置)。所以我们声明新变量:

std::shared_ptr<std::string> previous_message;

Then we amend our function according to the rules we specified:

然后我们根据我们指定的规则修改我们的函数:

void send_message(std::shared_ptr<std::string> msg)
{
    previous_message = 0;
    std::cout << *msg << std::endl;
    previous_message = msg;
}

So, before we start sending we discard the current previous message, and then after the send is complete we can store the new previous message. All good. Here's some test code:

所以,在我们开始发送之前,我们丢弃当前的前一条消息,然后在发送完成后,我们可以存储新的前一条消息。都好。下面是一些测试代码:

send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);

And as expected, this prints Hi!twice.

正如预期的那样,这会打印Hi!两次。

Now along comes Mr Maintainer, who looks at the code and thinks: Hey, that parameter to send_messageis a shared_ptr:

现在来了维护者先生,他看着代码并想:嘿,那个参数send_messageshared_ptr

void send_message(std::shared_ptr<std::string> msg)

Obviously that can be changed to:

显然可以改为:

void send_message(const std::shared_ptr<std::string> &msg)

Think of the performance enhancement this will bring! (Never mind that we're about to send a typically large message over some channel, so the performance enhancement will be so small as to be unmeasureable).

想想这将带来的性能提升!(没关系,我们将要通过某个通道发送通常很大的消息,因此性能增强将小到无法衡量)。

But the real problem is that now the test code will exhibit undefined behaviour (in Visual C++ 2010 debug builds, it crashes).

但真正的问题是,现在测试代码将表现出未定义的行为(在 Visual C++ 2010 调试版本中,它会崩溃)。

Mr Maintainer is surprised by this, but adds a defensive check to send_messagein an attempt to stop the problem happening:

维护者先生对此感到惊讶,但添加了防御性检查以send_message试图阻止问题发生:

void send_message(const std::shared_ptr<std::string> &msg)
{
    if (msg == 0)
        return;

But of course it still goes ahead and crashes, because msgis never null when send_messageis called.

但是当然它仍然继续并崩溃,因为msgsend_message调用时永远不会为空。

As I say, with all the code so close together in a trivial example, it's easy to find the mistake. But in real programs, with more complex relationships between mutable objects that hold pointers to each other, it is easy to makethe mistake, and hard to construct the necessary test cases to detect the mistake.

正如我所说,所有代码都在一个简单的例子中如此接近,很容易找到错误。但在实际方案,持有指向对方可变对象之间的关系更复杂,很容易做出错误,并努力构建必要的测试用例来检测错误。

The easy solution, where you want a function to be able to rely on a shared_ptrcontinuing to be non-null throughout, is for the function to allocate its own true shared_ptr, rather than relying on a reference to an existing shared_ptr.

简单的解决方案是,您希望函数能够始终依赖于shared_ptr非空值,即让函数分配自己的 true shared_ptr,而不是依赖于对现有 的引用shared_ptr

The downside is that copied a shared_ptris not free: even "lock-free" implementations have to use an interlocked operation to honour threading guarantees. So there may be situations where a program can be significantly sped up by changing a shared_ptrinto a shared_ptr &. But it this is not a change that can be safely made to all programs. It changes the logical meaning of the program.

缺点是复制的 ashared_ptr不是免费的:即使是“无锁”实现也必须使用互锁操作来兑现线程保证。因此,在某些情况下,可以通过将 a 更改shared_ptr为 a来显着加快程序速度shared_ptr &。但这并不是可以安全地对所有程序进行的更改。它改变了程序的逻辑含义。

Note that a similar bug would occur if we used std::stringthroughout instead of std::shared_ptr<std::string>, and instead of:

请注意,如果我们std::string在整个过程中使用而不是std::shared_ptr<std::string>, 而不是:

previous_message = 0;

to clear the message, we said:

为了清除消息,我们说:

previous_message.clear();

Then the symptom would be the accidental sending of an empty message, instead of undefined behaviour. The cost of an extra copy of a very large string may be a lot more significant than the cost of copying a shared_ptr, so the trade-off may be different.

那么症状将是意外发送空消息,而不是未定义的行为。一个非常大的字符串的额外副本的成本可能比复制 a 的成本要大得多shared_ptr,因此权衡可能会有所不同。

回答by Jon

I found myself disagreeing with the highest-voted answer, so I went looking for expert opinons and here they are. From http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

我发现自己不同意最高投票的答案,所以我去寻找专家意见,他们就在这里。来自http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter: "when you pass shared_ptr's, copies are expensive"

Herb Sutter:“当你通过 shared_ptr 时,副本很昂贵”

Scott Meyers: "There's nothing special about shared_ptr when it comes to whether you pass it by value, or pass it by reference. Use exactly the same analysis you use for any other user defined type. People seem to have this perception that shared_ptr somehow solves all management problems, and that because it's small, it's necessarily inexpensive to pass by value. It has to be copied, and there is a cost associated with that... it's expensive to pass it by value, so if I can get away with it with proper semantics in my program, I'm gonna pass it by reference to const or reference instead"

Scott Meyers:“当涉及到是按值传递还是按引用传递时,shared_ptr 没有什么特别之处。使用与用于任何其他用户定义类型完全相同的分析。人们似乎有这种看法,shared_ptr 以某种方式解决了所有管理问题,而且因为它很小,所以通过价值传递必然是廉价的。它必须被复制,并且有与之相关的成本......通过价值传递它是昂贵的,所以如果我能逃脱它在我的程序中具有适当的语义,我将通过引用 const 或引用来传递它”

Herb Sutter: "always pass them by reference to const, and very occasionally maybe because you know what you called might modify the thing you got a reference from, maybe then you might pass by value... if you copy them as parameters, oh my goodness you almost never need to bump that reference count because it's being held alive anyway, and you should be passing it by reference, so please do that"

Herb Sutter:“总是通过对 const 的引用传递它们,偶尔可能因为你知道你调用的东西可能会修改你从中获得引用的东西,也许然后你可能会通过值传递......如果你将它们作为参数复制,哦天哪,您几乎不需要增加引用计数,因为无论如何它都处于活动状态,您应该通过引用传递它,所以请这样做”

Update: Herb has expanded on this here: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/, although the moral of the story is that you shouldn't be passing shared_ptr's at all "unless you want to use or manipulate the smart pointer itself, such as to share or transfer ownership."

更新:Herb 对此进行了扩展:http: //herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/,尽管这个故事的寓意是你不应该通过shared_ptr 完全“除非您想使用或操纵智能指针本身,例如共享或转移所有权。”

回答by Mark Kegel

I would advise against this practice unless you and the other programmers you work with really, reallyknow what you are all doing.

我建议不要这样做,除非您和与您一起工作的其他程序员真的、真的知道你们在做什么。

First, you have no idea how the interface to your class might evolve and you want to prevent other programmers from doing bad things. Passing a shared_ptr by reference isn't something a programmer should expect to see, because it isn't idiomatic, and that makes it easy to use it incorrectly. Program defensively: make the interface hard to use incorrectly. Passing by reference is just going to invite problems later on.

首先,你不知道你的类的接口会如何发展,你想防止其他程序员做坏事。通过引用传递 shared_ptr 不是程序员应该期望看到的,因为它不是惯用的,并且很容易错误地使用它。防御性编程:使界面难以错误使用。通过引用传递只会在以后引发问题。

Second, don't optimize until you know this particular class is going to be a problem. Profile first, and then if your program really needs the boost given by passing by reference, then maybe. Otherwise, don't sweat the small stuff (i.e. the extra N instructions it takes to pass by value) instead worry about design, data structures, algorithms, and long-term maintainability.

其次,在您知道这个特定的类将成为问题之前不要进行优化。首先分析,然后如果你的程序真的需要通过引用传递来提升,那么也许吧。否则,不要为小事(即按值传递所需的额外 N 条指令)而烦恼,而要担心设计、数据结构、算法和长期可维护性。

回答by Johannes Schaub - litb

Yes, taking a reference is fine there. You don't intend to give the method shared ownership; it only wants to work with it. You could take a reference for the first case too, since you copy it anyway. But for first case, it takesownership. There is this trick to still copy it only once:

是的,在那里参考是可以的。您不打算让方法共享所有权;它只想使用它。您也可以参考第一种情况,因为无论如何您都可以复制它。但对于第一种情况,它需要所有权。有一个技巧仍然只复制一次:

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

You should also copy when you return it (i.e not return a reference). Because your class doesn't know what the client is doing with it (it could store a pointer to it and then big bang happens). If it later turns out it's a bottleneck (first profile!), then you can still return a reference.

您还应该在返回时进行复制(即不返回引用)。因为你的类不知道客户端用它做什么(它可以存储一个指向它的指针,然后发生大爆炸)。如果后来证明这是一个瓶颈(第一个配置文件!),那么您仍然可以返回一个引用。



Edit: Of course, as others point out, this only is true if you know your code and know that you don't reset the passed shared pointer in some way. If in doubt, just pass by value.

编辑:当然,正如其他人指出的那样,只有当您知道您的代码并且知道您没有以某种方式重置传递的共享指针时,这才是正确的。如果有疑问,只需按值传递。

回答by Magnus Hoff

It is sensible to pass shared_ptrs by const&. It will not likely cause trouble (except in the unlikely case that the referenced shared_ptris deleted during the function call, as detailed by Earwicker) and it will likely be faster if you pass a lot of these around. Remember; the default boost::shared_ptris thread safe, so copying it includes a thread safe increment.

通过shared_ptrs是明智的const&。它不太可能引起麻烦(除非在shared_ptr函数调用期间删除了引用的不太可能的情况,如 Earwicker 所详述的),并且如果您传递很多这些,它可能会更快。记住; 默认boost::shared_ptr是线程安全的,所以复制它包括一个线程安全的增量。

Try to use const&rather than just &, because temporary objects may not be passed by non-const reference. (Even though a language extension in MSVC allows you to do it anyway)

尝试使用const&而不仅仅是&,因为临时对象可能不会通过非常量引用传递。(即使 MSVC 中的语言扩展允许您无论如何都可以这样做)

回答by Greg Rogers

In the second case, doing this is simpler:

在第二种情况下,这样做更简单:

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

You can call it as

你可以称之为

only_work_with_sp(*sp);

回答by zmqiu

I would advocate passing shared pointer by const reference - a semantics that the function being passed with the pointer does NOT own the pointer, which is a clean idiom for developers.

我提倡通过 const 引用传递共享指针——一种语义,即通过指针传递的函数不拥有指针,这对开发人员来说是一个干净的习惯用法。

The only pitfall is in multiple thread programs the object being pointed by the shared pointer gets destroyed in another thread. So it is safe to say using const reference of shared pointer is safe in single threaded program.

唯一的缺陷是在多线程程序中,共享指针指向的对象在另一个线程中被销毁。所以可以肯定地说,在单线程程序中使用共享指针的 const 引用是安全的。

Passing shared pointer by non-const reference is sometimes dangerous - the reason is the swap and reset functions the function may invoke inside so as to destroy the object which is still considered valid after the function returns.

通过非常量引用传递共享指针有时是危险的 - 原因是函数可能在内部调用交换和重置函数,以便在函数返回后销毁仍然被认为有效的对象。

It is not about premature optimization, I guess - it is about avoiding unnecessary waste of CPU cycles when you are clear what you want to do and the coding idiom has firmly been adopted by your fellow developers.

我猜这与过早优化无关 - 这是关于在您清楚自己想要做什么并且编码习惯已被其他开发人员牢牢采用时避免不必要的 CPU 周期浪费。

Just my 2 cents :-)

只有我的 2 美分 :-)

回答by Sandy

It seems that all the pros and cons here can actually be generalised to ANY type passed by reference not just shared_ptr. In my opinion, you should know the semantic of passing by reference, const reference and value and use it correctly. But there is absolutely nothing inherently wrong with passing shared_ptr by reference, unless you think that all references are bad...

似乎这里的所有优点和缺点实际上都可以推广到通过引用传递的任何类型,而不仅仅是 shared_ptr。在我看来,你应该知道按引用、const 引用和值传递的语义并正确使用它。但是通过引用传递 shared_ptr 绝对没有本质上的错误,除非您认为所有引用都是坏的......

To go back to the example:

回到这个例子:

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

How do you know that sp.do_something()will not blow up due to a dangling pointer?

你怎么知道它sp.do_something()不会因为悬垂的指针而爆炸?

The truth is that, shared_ptr or not, const or not, this could happen if you have a design flaw, like directly or indirectly sharing the ownership of spbetween threads, missusing an object that do delete this, you have a circular ownership or other ownership errors.

事实是,shared_ptr 与否,const 与否,如果您有设计缺陷,例如sp在线程之间直接或间接共享所有权,滥用对象,则可能会发生这种情况delete this,您有循环所有权或其他所有权错误。

回答by peterchen

I would avoid a "plain" reference unless the function explicitely may modify the pointer.

我会避免使用“普通”引用,除非该函数可以明确修改指针。

A const &may be a sensible micro-optimization when calling small functions - e.g. to enable further optimizations, like inlining away some conditions. Also, the increment/decrement - since it's thread safe - is a synchronization point. I would not expect this to make a big difference in most scenarios, though.

const &在调用小函数时,A可能是明智的微优化 - 例如,启用进一步优化,例如内联某些条件。此外,增量/减量 - 因为它是线程安全的 - 是一个同步点。不过,我不希望这在大多数情况下会产生很大的不同。

Generally, you should use the simpler style unless you have reason not to. Then, either use the const &consistently, or add a comment as to why if you use it just in a few places.

通常,除非您有理由不这样做,否则您应该使用更简单的样式。然后,要么const &始终如一地使用它,要么在仅在少数地方使用它的情况下添加关于原因的评论。

回答by Malvineous

One thing that I haven't seen mentioned yet is that when you pass shared pointers by reference, you lose the implicit conversion that you get if you want to pass a derived class shared pointer through a reference to a base class shared pointer.

我还没有看到提到的一件事是,当您通过引用传递共享指针时,如果您想通过对基类共享指针的引用来传递派生类共享指针,则会丢失隐式转换。

For example, this code will produce an error, but it will work if you change test()so that the shared pointer is not passed by reference.

例如,此代码将产生错误,但如果您更改test()共享指针不通过引用传递,它将起作用。

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}