明确使用一个独立的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
}
因此,使用 shared_ptr
的引用会禁用该保证。因此,在您的第二种情况中:
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
你怎么知道 sp->do_something()
不会因为空指针而导致崩溃?
这完全取决于代码中“...”部分的内容。如果你在第一个“...”期间调用了某些东西,它会产生副作用(在代码的另一部分),清除了对同一对象的shared_ptr
,而且它恰好是该对象的最后剩余的不同shared_ptr
,那么你想使用的对象就不见了。
因此,有两种方式回答这个问题:
仔细检查整个程序的源代码,直到你确定对象在函数体内不会死亡。
将参数改回一个独立的对象而不是引用。
这里有一个应用的通用建议:在使用性能分析器以及明确测量所要进行的更改是否对性能有显著影响之前,不要为了性能而进行冒险的代码修改。
评论者JQ的更新
以下是一个人为制造的例子,它故意简单化了错误。在实际例子中,由于隐藏在多层真实细节中,错误并不那么明显。
我们有一个将消息发送到某个地方的函数。这可能是一个大型消息,因此我们使用指向字符串的shared_ptr
,而不是像传递到多个位置时会被复制的std::string
:
void send_message(std::shared_ptr<std::string> msg)
{
std::cout << (*msg.get()) << std::endl;
}
对于这个例子,我们只是将其“发送”到控制台。
现在我们想要添加一种记住上一个消息的功能。我们希望实现以下行为:存在一个变量,其中包含最近发送的消息,但在当前正在发送消息时,不应有先前的消息(变量应在发送之前被重置)。因此,我们声明了新变量:
std::shared_ptr<std::string> previous_message
然后,我们根据规定修改我们的函数:
void send_message(std::shared_ptr<std::string> msg)
{
previous_message = 0;
std::cout << *msg << std::endl;
previous_message = msg;
}
所以在我们开始发送之前,我们会丢弃当前的上一条消息,然后在发送完成后,我们可以存储新的上一条消息。所有的都很好。这是一些测试代码:
send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);
正如预期的那样,这将两次打印Hi!
现在维护人员登场了,他看着这段代码并想到:嘿,send_message
函数的这个参数是一个shared_ptr
:
void send_message(std::shared_ptr<std::string> msg)
显然,这可以被更改为:
void send_message(const std::shared_ptr<std::string> &msg)
想象一下这将带来的性能提升!(别管我们即将通过某个通道发送一个通常很大的消息,所以性能提升将非常微小,无法测量)。
但真正的问题是现在测试代码将表现出未定义的行为(在Visual C++ 2010调试版本中会崩溃)。
维护者先生对此感到惊讶,但在send_message
中添加了一个防御性检查,试图阻止这个问题的发生:
void send_message(const std::shared_ptr<std::string> &msg)
{
if (msg == 0)
return;
当然,它仍然会崩溃,因为在调用send_message
时,msg
从未为空。
就像我说的,在一个简单的示例中,所有代码都如此接近,很容易找到错误。但在真正的程序中,由于可变对象之间存在更复杂的关系,彼此持有指针,因此很容易犯错,并且很难构建必要的测试用例来检测错误。
如果您希望函数能够依赖于shared_ptr
始终保持非空状态,则简单的解决方案是让函数分配自己的真正的shared_ptr
,而不是依赖于对现有shared_ptr
的引用。
缺点是,复制shared_ptr
不是免费的:即使是“无锁”实现也必须使用交换操作来遵守线程保证。因此,在某些情况下,通过将shared_ptr
改为shared_ptr &
可以显著加快程序速度。但这不是一项可以安全应用于所有程序的更改。它改变了程序的逻辑含义。
请注意,如果我们始终使用std::string
而不是std::shared_ptr<std::string>
,那么将发生类似的错误,而不是:
previous_message = 0
为了清除该消息,我们说:
previous_message.clear();
那么这个症状将是意外发送空消息,而不是未定义的行为。复制一个非常大的字符串的额外成本可能比复制 shared_ptr
的成本显著得多,因此权衡可能会有所不同。