智能指针shared_ptr
,与unique_ptr
和weak_ptr
不同:
- 不像
unique_ptr
那么独断专制; - 也不像
weak_ptr
那么不负责任;
而是可以同时多个shared_ptr
指向同一块内存地址,这种特性使得可以使用shared_ptr
进行数据的共享。
初始化
当未显式初始化时,默认情况下使用nullptr进行初始化。
// define a nullptr pointing to s string, default initialization shared_ptr<string> p1; // nullptr cout << (s == nullptr) << endl;
可以对
shared_ptr
进行赋值操作,类似于普通的指针类型。shared_ptr<string> p1(new string("hello, world")); cout << *p1 << endl; // assignment if(p1) { *p1 = "hi"; } // use * access the object pointer points to cout << *p1 << endl; /** hello, world hi */
可以使用
make_shared
进行初始化,是推荐使用方式,安全且不易出错;This function allocates and initializes an object in dynamic memory and returns a shared_ptr that points to that object.
shared_ptr<int> p3 = make_shared<int>(42); shared_ptr<string> p4 = make_shared<string>(10, '9'); // use auto for simplification auto p6 = make_shared<vector<string>>();
- 在
make_shared
中,使用该指针所指向类型的构造函数来初始化。 - 无参的情况下,则为value initialization
- 在
与new结合使用
类似于
unique_ptr
,shared_ptr
也可以使用new
来支持初始化。// error, 类型转换不支持 shared_ptr<int> p1 = new int(1024); // ok, direct initialization shared_ptr<int> p2(new int(42))
一些初始化的方式总结如下:
copy & assign
When we copy or assign a
shared_ptr
, eachshared_ptr
keeps track of how many other shared_ptrs point to the same object.
每个shared_ptr都有一个绑定的counter,即reference count,用来表示有多少个shared_ptr
共享指向的对象。当shared_ptr的reference count变为0后,则会导致其指向的object被free,此时会调用该obejct的析构函数完成。
// int to which r points has one user
auto r = make_shared<int>(42);
// assign to r, making it point to a different address
// increase the use count for the object to which q points
// reduce the use count of the object to which r had pointed
// the object r had pointed to has no users; that object is automatically freed
r = q;
reference count的变化受到以下事件的影响:
增加reference count;
- shared_ptr被copy;
- 用该shared_ptr初始化另一个shared_ptr;
shared_ptr<string> p1(new string("hello, world")); cout << p1.use_count() << endl; shared_ptr<string> p2(p1); cout << p1.use_count() << endl; shared_ptr<string> p3 = p1; cout << p1.use_count() << endl; /** output: 1 2 3 */
减少reference count;
- shared_ptr被赋予新值;
shared_ptr<string> p1(new string("hello, world")); cout << p1.use_count() << endl; p1 = nullptr; cout << p1.use_count() << endl; /** output: 不会崩溃 1 0 */
- 该shared_ptr超出其定义的scope后,其被自动destroyed;
auto func() -> shared_ptr<string> { shared_ptr<string> p1(new string("hello, world")); cout << p1.use_count() << endl; shared_ptr<string> p2(p1); cout << p1.use_count() << endl; return p1; } int main() { // p1 and p1 are destroyed, p is generated. auto p = func(); cout << p.use_count() << endl; return 0; } /** output: 1 2 1 */
函数中使用
函数返回值
当作为函数返回值时,可以直接返回shared_ptr
。
// factory returns a shared_ptr pointing to a dynamically allocated object
shared_ptr<Foo> factory(T arg) {
// shared_ptr will take care of deleting this memory
return make_shared<Foo>(arg);
}
当作为函数返回值返回时,函数内部的指针会被destroyed,当外部的函数返回值没有“接盘侠”时,这块内存因为reference count变为0,就会被系统释放。
但是当返回值被赋值给新变量时,则这块内存的引用计数为1,就不会被释放,destroy的只有函数内部指向这块内存的shared_ptr
。
作为函数参数
unique_ptr作为函数参数进行传递时,有一些限制,因为它不能copy和assign,所以只能按照引用传递参数,或者释放控制权,或者重新使用make_unique赋值,如C++内存管理:智能指针与unique_ptr所述。
而shared_ptr
可以记进行copy和assign,因此限制会少一些。
一些关于将智能指针作为参数传递的推荐使用方式如下:
- C++ Core Guidelines
- C++ Core Guidelines: Passing Smart Pointers
- Move smart pointers in and out functions in modern C++
- Arguments and Smart Pointers
pass by value
这种方式中,与普通的对象类似,当传入shared_ptr
时,进行了copy,生成了一个新的智能指针,并且两个指针指向同一块内存,但是两个指针被存在不同的地址,且此时的reference count变成了2。
void func(shared_ptr<string> sp){
cout << *sp << endl;
cout << &sp << endl;
cout << sp.use_count() << endl;
}
int main()
{
shared_ptr<string> sp(new string("hello, world"));
// ok, this kind of form is accepted.
func(shared_ptr<string>(new string("hello, world")));
cout << &sp << endl;
func(sp);
return 0;
}
/**
output:
0x7fffc9825e90
hello, world
0x7fffc9825ea0
2
/
pass by reference
此时,只需要将func函数的参数改为引用即可,得到的输出完全不一样了,引用计数并不会增加,且智能指针是同一个。
void func(shared_ptr<string> &sp){
cout << *sp << endl;
cout << &sp << endl;
cout << sp.use_count() << endl;
}
int main()
{
shared_ptr<string> sp(new string("hello, world"));
cout << &sp << endl;
func(sp);
return 0;
}
/**
output:
0x7ffe147b9db0
hello, world
0x7ffe147b9db0
1
*/
shared_ptr的缺点
The program will execute correctly but may waste memory if you neglect to destroy shared_ptrs that the program does not need.
当程序不能及时将最后一个shared_ptr销毁,则其指向的内存会一直存在,导致浪费。这种问题在容器中可能更容易忽略。
换句话说,因为shared_ptr及其对应内存的销毁,与reference count有紧密的关系,而且一般不建议显式操作,但是如果想要shared_ptr一直存在呢,可以将它们放到vector等容器中,但是对于容器中不需要的shared_ptr,也要及时erase掉,相关问题可以参考这里。
一些使用技巧和注意事项
自己死亡不牵连别人
vector<string> s1;
{
vector<string> s2 = {"hello", "world"};
s1 = s2;
s1.push_back("haha")
for(auto si: s2) {
cout << si << endl;
}
}
for(auto si: s1) {
cout << si << endl;
}
/**
output:
hello
world
hello
world
haha
*/
当在block scope中对s1赋值时,当该scope结束后,s2就被回收了,但是不影响s1,因为s2中的元素已经被copy到s1中,两者没有任何关系,因此对其中任意一个修改,不会影响另一个。
当scope结束后,s2被回收,其中对应的元素也会被释放,对于vector来说,其指针存储在stack中,而对应的元素是动态分配在heap中的。此时s1中被赋值的元素仍然存在。
除了上述这种方式,s2的死亡不影响s1,但是s1和s2之间的元素是不能共享的,有时需要进行共享,这里可以使用shared_ptr
实现。
class sharedObject
{
public:
sharedObject() = default;
sharedObject(shared_ptr<string> name, shared_ptr<string> address) : m_name(name), m_address(address) {}
shared_ptr<string> m_name;
shared_ptr<string> m_address;
};
int main()
{
sharedObject so1;
{
shared_ptr<string> name(new string("john"));
shared_ptr<string> address(new string("sh"));
sharedObject so2(name, address);
so1 = so2;
*so2.m_name = "Ram";
}
cout << *so1.m_name << endl;
cout << *so1.m_address << endl;
return 0;
}
/**
output:
Ram
sh
*/
其中,so1和so2共享其中的两个成员变量,这并没有通过类的静态成员实现,而且类的静态成员变量在所有对象之间共享,但是这种使用shared_ptr
的方式可以指定进行共享的对象,在更细粒度上控制共享的范围。
谨慎使用普通指针访问智能指针指向的内存
It is dangerous to use a built-in pointer to access an object owned by a smart pointer, because we may not know when that object is destroyed.
用智能指针得到的内存对象,就一直用智能指针来访问,因为这些不会因为scope的结束导致对象被destroyed,但是普通的指针会。看一个例子:
void func(shared_ptr<string> sp){
cout << sp.use_count() << endl;
}
int main()
{
string *p1 = new string("hello, world");
cout << *p1 << endl;
func(shared_ptr<string>(p1));
cout << *p1 << endl;
return 0;
}
/**
output:
hello, world
1
*/
当完成了func函数的调用,再使用*p1访问内存中的数据时已经不起作用了,此时p1成为dangling pointer,因为其指向的内存,在func中,当参数sp超过func的作用域后,连同智能指针sp都被销毁了,因为无法访问了。
谨慎使用get
Use get only to pass access to the pointer to code that you know will not delete the pointer. In particular, never use get to initialize or assign to another smart pointer.
这个函数又将智能指针与普通指针混用了,因为get返回的是智能指针管理的普通指针,如果返回了该普通指针,就会脱离智能指针为其建立的一系列安全机制,比如自动删除内存等,会导致内存误删、重复删除等问题。
看如下代码:
int main()
{
shared_ptr<string> sp1(new string("hello, world"));
string *p = sp1.get();
{
shared_ptr<string> sp2(p);
}
cout << *sp1 << endl;
return 0;
}
运行时出现了重复使用内存的问题,如下:
写数据时要验证
因为shared_ptr
指向的内存对象是在多个指针之间共享,因此当其中一个指针更改其中的值,会影响到其他指针的使用,为了避免这种情况,需要在写入新数据之前进行验证,判断自己是不是唯一的使用者。
share_ptr<string> sp(new string("hello, world"));
// equal to sp.use_count() == 1.
if(!sp.unique()){
sp.reset(new string(*p));
}
*sp += " haha";
cout << *sp << endl;
/**
output:
hello, world haha
*
这里,当sp是唯一的使用者时,sp可以直接操作数据;但是如果有其他的使用者时,sp需要释放其原来的指向的对象,并重新申请一块内存,并将原来内存中的数据复制过来,然后将新生成的shared_ptr
指向这块新内存,这就避免了对数据的负面影响。
其他相关操作
对于智能指针的操作,有些是共有的API,shared_ptr和unique_ptr都可以使用,如下:
还有一些是shared_ptr独有的API,如下所示:
参考资料
- C++ Primer - 12.1.1 The shared_ptr class
- C++ Primer - 12.1.3 Using shared_ptrs with new