abi*_*gli 113 c++ boost pass-by-reference pass-by-value shared-ptr
如果我有一个需要使用a的函数shared_ptr,那么传递它的引用会不会更有效(所以为了避免复制shared_ptr对象)?有哪些可能的不良副作用?我设想了两种可能的情况:
1)在函数内部,复制由参数组成,如
ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)
{
...
m_sp_member=sp; //This will copy the object, incrementing refcount
...
}
Run Code Online (Sandbox Code Playgroud)
2)在函数内部仅使用参数,例如
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
Run Code Online (Sandbox Code Playgroud)
我无法在两种情况下都看到传递boost::shared_ptr<foo>by值而不是引用的充分理由.按值传递只会"暂时"增加由于复制而引用的引用计数,然后在退出函数范围时减少它.我忽略了什么吗?
只是为了澄清,在阅读了几个答案之后:我完全赞同过早优化的问题,而且我总是试着先在热点上进行首次剖析.如果你知道我的意思,我的问题更多来自纯粹的技术代码观点.
Jon*_*Jon 111
我发现自己不同意最高投票的答案,所以我去寻找专家意见,他们在这里.来自http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything
Herb Sutter:"当你通过shared_ptr时,副本很贵"
Scott Meyers:"当涉及到你是通过值传递它还是通过引用传递它时,shared_ptr没什么特别的.使用与你用于任何其他用户定义类型的完全相同的分析.人们似乎有这种看法,shared_ptr以某种方式解决所有的管理问题,因为它很小,所以通过价值传递必然是便宜的.它必须被复制,并且有相关的成本......按价值传递它是昂贵的,所以如果我可以逃脱它在我的程序中有适当的语义,我将通过引用传递给const或引用"
Herb Sutter:"总是通过参考const来传递它们,偶尔也许是因为你知道你所谓的可能会修改你从中得到引用的东西,也许你可能会通过值传递...如果你把它们复制为参数哦我的天哪,你几乎从不需要碰到那个引用计数,因为它无论如何都被保留了,你应该通过引用传递它,所以请这样做"
更新:Herb在此扩展了这个:http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/,尽管这个故事的寓意是你不应该通过shared_ptr完全"除非您想使用或操纵智能指针本身,例如共享或转让所有权."
Dan*_*ker 109
不同shared_ptr实例的要点是保证(尽可能)只要shared_ptr它在范围内,它指向的对象仍然存在,因为它的引用计数至少为1.
Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
// sp points to an object that cannot be destroyed during this function
}
Run Code Online (Sandbox Code Playgroud)
因此,通过使用对a的引用shared_ptr,您将禁用该保证.所以在你的第二个案例中:
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
Run Code Online (Sandbox Code Playgroud)
你怎么知道sp->do_something()由于空指针而不会爆炸?
这一切都取决于代码的那些'......'部分.如果你在第一个'...'中调用某些东西会产生副作用(在代码的另一部分的某个地方)清除shared_ptr同一个对象会怎么样?如果它恰好是该shared_ptr对象唯一的不同之处呢?再见对象,就在你要尝试使用它的地方.
所以有两种方法可以回答这个问题:
仔细检查整个程序的来源,直到确定对象不会在函数体中死亡.
将参数更改回不同的对象而不是引用.
适用于此处的一般建议:为了提高性能,不要费心对代码进行有风险的更改,直到您在剖析器中将实际情况计时产品并最终确定您要进行的更改将使性能差异显着.
评论者JQ的更新
这是一个人为的例子.它故意简单,所以错误很明显.在实际例子中,错误并不那么明显,因为它隐藏在真实细节的层次中.
我们有一个函数可以在某处发送消息.它可能是一个很大的消息,所以std::string我们使用a shared_ptr到字符串,而不是使用可能被复制的消息,因为它被传递到多个地方:
void send_message(std::shared_ptr<std::string> msg)
{
std::cout << (*msg.get()) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
(我们只是将它"发送"到这个例子的控制台).
现在我们要添加一个工具来记住上一条消息.我们需要以下行为:必须存在包含最近发送的消息的变量,但是当前正在发送消息时,必须没有先前的消息(该变量应该在发送之前重置).所以我们声明了新的变量:
std::shared_ptr<std::string> previous_message;
Run Code Online (Sandbox Code Playgroud)
然后我们根据我们指定的规则修改我们的函数:
void send_message(std::shared_ptr<std::string> msg)
{
previous_message = 0;
std::cout << *msg << std::endl;
previous_message = msg;
}
Run Code Online (Sandbox Code Playgroud)
因此,在我们开始发送之前,我们丢弃当前的上一条消息,然后在发送完成后,我们可以存储新的上一条消息.都好.这是一些测试代码:
send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);
Run Code Online (Sandbox Code Playgroud)
正如预期的那样,这会打印Hi!两次.
现在来看看Maintainer先生,他看着代码并想:嘿,那个参数send_message是shared_ptr:
void send_message(std::shared_ptr<std::string> msg)
Run Code Online (Sandbox Code Playgroud)
显然可以改为:
void send_message(const std::shared_ptr<std::string> &msg)
Run Code Online (Sandbox Code Playgroud)
想想这将带来的性能提升!(没关系,我们将要通过某个频道发送一个典型的大型消息,因此性能提升将非常小,以至于无法测量).
但真正的问题是,现在测试代码将呈现未定义的行为(在Visual C++ 2010调试版本中,它会崩溃).
Maintainer先生对此感到惊讶,但send_message为了阻止问题的发生而增加了防御性检查:
void send_message(const std::shared_ptr<std::string> &msg)
{
if (msg == 0)
return;
Run Code Online (Sandbox Code Playgroud)
但当然它仍然会继续崩溃,因为msg在send_message调用时它永远不会为null .
正如我所说,所有代码在一个简单的例子中如此接近,很容易找到错误.但在实际方案,持有指向对方可变对象之间的关系更复杂,很容易做出错误,并努力构建必要的测试用例来检测错误.
简单的解决方案是,您希望函数能够依赖于shared_ptr始终为非null的函数,该函数可以为函数分配自己的true shared_ptr,而不是依赖于对现有函数的引用shared_ptr.
缺点是复制的a shared_ptr不是免费的:即使是"无锁"的实现也必须使用互锁操作来保证线程保证.因此,有些情况下可以通过将a更改shared_ptr为a 来显着加快程序的速度shared_ptr &.但这不是一个可以安全地对所有程序进行的改变.它改变了程序的逻辑含义.
请注意,如果我们使用的是std::string整个而不是代码,则会发生类似的错误std::shared_ptr<std::string>:
previous_message = 0;
Run Code Online (Sandbox Code Playgroud)
为了清除这个信息,我们说:
previous_message.clear();
Run Code Online (Sandbox Code Playgroud)
然后症状是意外发送空消息,而不是未定义的行为.一个非常大的字符串的额外副本的成本可能比复制a的成本更重要shared_ptr,因此权衡可能是不同的.
Mar*_*gel 22
除非你和你合作的其他程序员真的知道你们在做什么,否则我会反对这种做法.
首先,你不知道你的类的接口是如何演变的,你想要阻止其他程序员做坏事.通过引用传递shared_ptr并不是程序员应该看到的东西,因为它不是惯用的,这使得它很容易被错误地使用.防御性程序:使界面难以正确使用.通过引用传递将在以后引发问题.
其次,在你知道这个特定类会成为一个问题之前不要进行优化.首先描述,然后如果你的程序真的需要通过引用传递给出的提升,那么也许.否则,不要为小东西(即通过值传递额外的N指令)而烦恼,而是担心设计,数据结构,算法和长期可维护性.
Joh*_*itb 18
是的,参考就好了.您不打算给该方法共享所有权; 它只想用它.您也可以参考第一个案例,因为无论如何你都要复制它.但对于第一种情况,它需要所有权.有这个技巧仍然只复制一次:
void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
m_sp_member.swap(sp);
}
Run Code Online (Sandbox Code Playgroud)
您还应该在退货时复制(即不返回参考).因为你的类不知道客户端正在做什么(它可以存储指向它的指针,然后发生大爆炸).如果它后来证明它是一个瓶颈(第一个配置文件!),那么你仍然可以返回一个引用.
编辑:当然,正如其他人所指出的那样,只有你知道你的代码并且知道你没有以某种方式重置传递的共享指针,这才是真的.如果有疑问,只需按值传递.
Mag*_*off 11
通过shared_ptrs 是明智的const&.它不会引起麻烦(除非在shared_ptr函数调用期间删除引用的不太可能的情况,如Earwicker所详述),如果你传递了很多这些内容,它可能会更快.记得; 默认boost::shared_ptr是线程安全的,因此复制它包括线程安全增量.
尝试使用const&而不仅仅是&因为临时对象可能不会被非const引用传递.(尽管MSVC中的语言扩展允许您无论如何都可以这样做)
Gre*_*ers 10
在第二种情况下,这样做更简单:
Class::only_work_with_sp(foo &sp)
{
...
sp.do_something();
...
}
Run Code Online (Sandbox Code Playgroud)
你可以称之为
only_work_with_sp(*sp);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
49978 次 |
| 最近记录: |