从cppreference中我了解到std::shared_ptr
有一个构造函数:
template< class Y > explicit shared_ptr( Y* ptr );
我试了一段代码,如下所示:
#include <string>
#include <memory>
#include <iostream>
int main(void) {
/// block 1
{
std::shared_ptr<std::string> s1(new std::string("good"));
std::shared_ptr<std::string> s2(s1.get()); /// s2
std::cerr << s2.use_count() << std::endl;
}
/// block 2
{
std::shared_ptr<int> i1(new int(1));
std::shared_ptr<int> i2(i1.get()); /// i2
std::cerr << i2.use_count() << std::endl;
}
return 0;
}
在块1引起段故障,而在块2不引起段故障,但两个use_count
都是1。我能想到的区别是int
是一个基本类型,而std::string
是由分配器管理的。
我读gcc-4.9
的bits/shared_ptr.h
,发现这个构造函数有一个post条件:
use_count() == 1 && get() == __p
问题1:
std::shared_ptr
不应该用一个被另一个智能指针引用的原始指针构造吗?从这个意义上说,使用这个构造函数的首选方式是如下方式吗?
std::shared_ptr<T>(new T(...));
问题2:
标准是否对这个构造函数有明确的要求,或者这个post条件只适用于libstdc++
?
这两种情况都是无效的std::shared_ptr
使用
您不能将相同的原始指针传递给两个 std::shared_ptr
构造函数并期望定义良好的结果。两个 std::shared_ptr
都认为它们拥有指针,并在超出作用域时尝试删除它。
这是一个双重自由,是无效的。
如果你想有两个std::shared_ptr
管理同一个对象,你可以用一个原始指针构造其中一个(或者,更好的是,使用std::make_shared
),然后从第一个复制构造/分配第二个。这样,只有当这些std::shared_ptr
的最后一个超出作用域时,内存才会被释放(并且对象的析构函数被触发)。
在第一种情况下而不是第二种情况下出现分段错误的原因可能是因为int
是一个平凡的类型,因此您没有通过一个释放的指针来运行int
的析构函数,因为它没有。
这是未定义的行为,我们可以在std::shared_ptr构造器的cppreference部分看到这一点,上面写着(强调我的):
使用对象的原始指针重载构造shared_ptr如果已经由shared_ptr管理,则会导致未定义行为,即使对象是派生的类型Std::enable_shared_from_this(换句话说,原始指针重载假定指向对象的所有权)。
两个shared_ptr
都将尝试删除他们认为自己现在拥有的对象。我们知道这是未定义的行为,通过查看c++标准草案3.7.4.2
释放函数,其中说:
如果在标准的库中不是空指针值(4.10)的指针函数所引用的存储将被释放对象的任何部分的所有指针都无效分配存储空间。间接通过无效的指针值和传递一个无效的指针值给释放函数未定义的行为。[…]
所有权只能通过使用副本构造或副本赋值来共享。相同的cppreference页面提供了一个使用复制构造函数的正确示例:
std::cout << "constructor with objectn";
std::shared_ptr<Foo> sh2(new Foo);
std::shared_ptr<Foo> sh3(sh2); // <- using copy construction
创建shared_ptr
的另一种方法是使用std::make_shared,它更安全:
而且,像f(std::shared_ptr(new int(42)), g())这样的代码可以如果g抛出异常,因为可能会调用g(),则会导致内存泄漏在new int(42)之后,shared_ptr的构造函数之前。这在f(std::make_shared(42), g())中不会发生,因为两个函数呼叫永远不会交错。