C++中,空std::shared_ptr和null std::shared_ptr有什么区别?

87
cplusplus.comshared_ptr页面指出了std::shared_ptremptynull之间的区别。 cppreference.com没有明确说明这种区别,但在对std::shared_ptr行为的描述中同时使用了“empty”和与nullptr的比较。 shared_ptr是空的和null的有什么区别? 这种混合行为指针是否有任何用途? 非空的null shared_ptr是否有意义? 在正常使用情况下(即如果您没有显式构造一个),是否会出现空但非null的shared_ptr
如果使用Boost版本而不是C++11版本,上述答案是否有所改变?
2个回答

88

这是 shared_ptr 行为的一个奇怪角落。它有一个构造函数,允许你创建一个既“拥有”某个东西,又“指向”其他东西的 shared_ptr

template< class Y > 
shared_ptr( const shared_ptr<Y>& r, T *ptr );

使用此构造函数构建的shared_ptr将与r共享所有权,但指向ptr指向的任何内容(即调用get()operator->()都会返回ptr)。这在ptr指向r拥有的对象的子对象(例如数据成员)的情况下非常方便。

你链接的页面称没有拥有任何内容的shared_ptr为空empty,而指向没有任何内容(即其get() == nullptr)的shared_ptr则为空指针null。(标准使用empty表示此意义;而不是null。)你可以构造一个空但非空指针的shared_ptr,但它并不是很有用。一个空但非空指针的shared_ptr实际上是一个非拥有指针,可以用于一些奇怪的事情,比如将指向堆栈上分配的某个对象的指针传递给需要shared_ptr的函数(但我建议首先揍那个把shared_ptr放在API内部的人)。

boost::shared_ptr还具有此构造函数,被称为别名构造函数


8
值得注意的是:C++11 § 20.7.2.2.1 (p16) “注意:此构造函数允许创建一个具有非NULL存储指针的空shared_ptr实例。”还值得提到前面的注释(p15):“为了避免悬挂指针的可能性,使用此构造函数的用户必须确保p至少在r的所有权组被销毁之前保持有效。” 这是一个很少使用的构造方法。 - WhozCraig
@Cubbi 一个 shared_ptr,其 get() 返回 nullptr,无论它是否拥有任何内容,都与 nullptr 相等。 - T.C.
3
一个空的但非空的shared_ptr在确保所有拥有指针超出作用域后(即使出现异常!)执行某些函数时可能是有用的。不确定是否现在有一个专门的类来实现这个功能。 - coldfix
@T.C. 你可以使用它来调用一个以空指针作为第一个参数的函数:P。此外,如果你只关心某个函数(或lambda表达式)在特定时间执行,并且不知道要传递给它任何对象,那么你可以使用null来明确表示。我承认,在没有指针功能的情况下使用专用类会更加明确和好,但对于小型项目来说也需要更多的工作;)。请参见使用shared_ptr在块退出时执行代码 - coldfix
2
别名构造函数源自彭博社,并在实现于Boost之前被提议为标准(参见N1851)。我更喜欢标准术语“与r共享所有权”而不是“拥有r拥有的任何东西”的措辞。 - Jonathan Wakely
显示剩余4条评论

12

空的shared_ptr和null的shared_ptr之间有什么区别?

空的shared_ptr没有控制块,其使用计数被认为为0。空的shared_ptr的复制是另一个空的shared_ptr。它们都是独立的shared_ptr,因为它们没有共享公共控制块。可以使用默认构造函数或带有nullptr的构造函数构造空的shared_ptr

非空的null shared_ptr具有可以与其他shared_ptr共享的控制块。非空null shared_ptr的副本是与原始shared_ptr共享相同控制块的shared_ptr,所以使用计数不为0。可以说所有的shared_ptr的副本共享相同的nullptr。可以使用对象类型的空指针(而不是nullptr)构造非空的null shared_ptr

以下是示例:

#include <iostream>
#include <memory>

int main()
{
    std::cout << "std::shared_ptr<int> ptr1:" << std::endl;
    {
        std::shared_ptr<int> ptr1;
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;

    std::cout << "std::shared_ptr<int> ptr1(nullptr):" << std::endl;
    {
        std::shared_ptr<int> ptr1(nullptr);
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;

    std::cout << "std::shared_ptr<int> ptr1(static_cast<int*>(nullptr))" << std::endl;
    {
        std::shared_ptr<int> ptr1(static_cast<int*>(nullptr));
        std::cout << "\tuse count before copying ptr: " << ptr1.use_count() << std::endl;
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "\tuse count  after copying ptr: " << ptr1.use_count() << std::endl;        
        std::cout << "\tptr1 is " << (ptr1 ? "not null" : "null") << std::endl;
    }
    std::cout << std::endl;

    return 0;
}

它输出:

std::shared_ptr<int> ptr1:
    use count before copying ptr: 0
    use count  after copying ptr: 0
    ptr1 is null

std::shared_ptr<int> ptr1(nullptr):
    use count before copying ptr: 0
    use count  after copying ptr: 0
    ptr1 is null

std::shared_ptr<int> ptr1(static_cast<int*>(nullptr))
    use count before copying ptr: 1
    use count  after copying ptr: 2
    ptr1 is null

http://coliru.stacked-crooked.com/a/54f59730905ed2ff


1
我认为这更好地回答了为什么我们必须在shared_ptr的自定义删除器中检查null。在shared_ptr的自定义删除器中检查nullptr是否有意义? - David Lee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接