[C++]智能指针unique_ptr,shared_ptr,weak_ptr
Posted ouyangshima
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C++]智能指针unique_ptr,shared_ptr,weak_ptr相关的知识,希望对你有一定的参考价值。
C++中对于动态内存的使用非常严格,一次申请必须对应一次释放,否则将造成内存泄漏。这也要求程序员格外小心,比如如下示例:
void getStr()
std::string * pstr = new std::string();//pstr为局部变量
*pstr = "Hello world";
....
return;
当该方法执行完毕后,局部变量pstr所占用内存自动释放,但其指向的内存则一直驻留。必须通过delete pstr来释放,因此这里将造成内存泄漏。
还有一种情况就是当程序因异常而终止时:
#include <stdexcept>
void getStr()
char * pch = new char[1024];
...
if (true)
throw logic_error("throw a exception");
delete [] pch;
虽然在这里没有忘记delete释放内存,但是当程序执行到throw时,将不再执行throw之后的语句,因此导致内存泄漏。针对于这个例子,虽然可以通过如下的方式避免这个问题,但依旧需要非常小心:
#include <stdexcept>
void getStr()
char * pch = new char[1024];
...
if (true)
try
throw logic_error("throw a exception");
catch(...)
delete [] pch;
throw;
delete [] pch;
为了让程序员不再因内存泄漏而操心,C++11中提供了几个用于动态内存管理的智能指针。
智能指针的原理
如果在指针对象过期时,让它的析构函数删除它指向的内存,那不就避免内存的泄漏了吗?比如当自动变量pstr被销毁时,让他的析构函数可以释放它指向的内。
智能指针正是采用这种方式实现的,在智能指针类中,定义了类似指针的对象,然后将new获得的地址赋给这个对象,当智能指针过期后,其析构函数将使用delete来释放这块内存。
使用智能指针
智能指针模板类位于头文件memory中,因此使用时必须包含该头文件。memory中提供了四种智能指针模板类:auto_ptr,unique_ptr,shared_ptr,weak_ptr,其中auto_ptr在C++11中被弃用,在C++17中被移除,因此主要学习其余三种。
unique_ptr
unique_ptr类模板如下:
template<class T, class Deleter = std::default_delete<T>>
class unique_ptr;
template <class T,class Deleter>
class unique_ptr<T[], Deleter>;
因此,unique_ptr有两个版本:
- 管理个对象(以 new 分配)
- 管理动态分配的对象数组(以 new[] 分配)
构造方法
unique_ptr的构造方法有多个,但常用如下几个个:
constexpr unique_ptr() noexcept;
constexpr unique_ptr (nullptr_t) noexcept : unique_ptr()
explicit unique_ptr(pointer p) noexcept;
unique_ptr通过建立所有权概念来管理对象,也就是说,对于特定的对象,只能有一个智能指针可以拥有它。因此:
- 不能使用一个unique_ptr对象来初始化另一个unique_ptr对象,其拷贝构造不可用:unique_ptr (const unique_ptr&) = delete;
- 不能使用一个unique_ptr对象来给另一个unique_ptr赋值,其赋值运算符不可用:unique_ptr& operator= (const unique_ptr&) = delete.
然而,当试图将一个unique_ptr对象赋给另一个unique_ptr时,如果源unique_ptr是一个临时右值,则编译器允许这样做,如:
std::unique_ptr<Person> get_uptr(Person* p)
std::unique_ptr<Person> temp (p);
return temp;
std::unique_ptr<Person> uptr4 = get_uptr(new Person("XiaoWang"));
这是通过移动构造来区分的。
如果需要将一个unique_ptr对象赋给另一个unique_ptr,则使用std::move()函数;
成员函数
- get():返回指向被管理对象的指针。
- reset():替换被管理对象;
- release(): 返回一个指向被管理对象的指针,并释放所有权.
- swap():替换被管理对象;
- operator bool:检查是否有关联的被管理对象.
下面为使用unique_ptr示例:
#include <iostream>
#include <memory>
#include <string>
class Person
private:
std::string name;
public:
Person(const std::string& name):name(name)
std::cout << "Person " << name << " created." << std::endl;
void show() const
std::cout << "Name: " << name << std::endl;
~Person()
std::cout << "Person " << name << " deleted." << std::endl;
;
int main()
// 1.创建unique_ptr管理Person*对象
Person* p = new Person("XiaoQiang");
std::unique_ptr<Person> uptr1;
std::unique_ptr<Person> uptr2(new Person("Zhangsan"));
// 2.不能用一个unique_ptr来初始化另一个unique_ptr
//拷贝构造不可用:unique_ptr (const unique_ptr&) = delete
//std::unique_ptr<Person> uptr3 = uptr2;
// 3.同一对象只能被一个unique_ptr拥有,否则编译出错
//赋值运算符不可用:operator=(const unique_ptr& ) = delete;
//uptr1 = uptr2;
// 3.get()返回管理对象的指针
Person* pt = uptr2.get();
pt->show();
// 4.reset()替换管理对象,原来对象将销毁
uptr2.reset(p);
uptr2.get()->show();
// 5.release()返回管理对象,并释放所有权
uptr2.release()->show();
// 6.operator bool 判断是否有关联对象
if(uptr2)
uptr2->show();
else
std::cout << "uprt is null" << std::endl;
// 7.swap()交换管理对象
std::unique_ptr<Person> uptr3(new Person("LiSi"));
std::unique_ptr<Person> uptr4(new Person("James"));
uptr3.swap(uptr4);
uptr3->show();
// 8.std::move()函数将一个unique_ptr对象赋给另一个unique_ptr对象
uptr1 = std::move(uptr4);
uptr1->show();
// 9.c++14中,使用std::make_unique(T&& t)创建unique_ptr,参数为右值引用
auto uptr5 = std::make_unique<Person>("LaoWang");
uptr5->show();
return 0;
/*
@ubuntu:~$ g++ uptr.cpp -o uptr --std=c++14
@ubuntu:~$ ./uptr
Person XiaoQiang created.
Person Zhangsan created.
Name: Zhangsan
Person Zhangsan deleted.
Name: XiaoQiang
Name: XiaoQiang
uprt is null
Person LiSi created.
Person James created.
Name: James
Name: LiSi
Person LaoWang created.
Name: LaoWang
Person LaoWang deleted.
Person James deleted.
Person LiSi deleted.
*/
shared_ptr
shared_ptr通过引用计数机制来管理对象,多个 shared_ptr 对象可占有同一对象。可以通过成员函数use_count()返回shared_ptr所指对象的引用计数。它的类模板如下:
template <class T> class shared_ptr;
构造方法
其常用构造方法如下:
constexpr shared_ptr() noexcept;
constexpr shared_ptr(nullptr_t) : shared_ptr()
template <class U> explicit shared_ptr (U* p);
shared_ptr (const shared_ptr& x) noexcept;
template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;
template <class U> explicit shared_ptr (const weak_ptr<U>& x);
template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);
成员方法
- get():返回管理对象的指针;
- swap():交换管理对象;
- reset():替换管理对象,原对象将销毁;
- use_count():返回shared_ptr所指对象的引用计数;
- unique():判断所管理对象是否仅由当前shared_ptr的实例管理;
- operator bool:检查是否有关联的管理对象;
下面为使用shared_ptr的示例:
#include <iostream>
#include <memory>
#include <string>
int main()
std::string* str = new std::string("I'm shared_ptr.");
// 1.创建shared_ptr
std::shared_ptr<std::string> sptr1;
std::shared_ptr<std::string> sptr2(str);
sptr1 = sptr2;
std::shared_ptr<std::string> sptr3(new std::string("Zhangsan"));
// 2.使用右值unique_ptr创建shared_ptr
std::shared_ptr<std::string> sptr4(std::unique_ptr<std::string>(
new std::string("I'm unique_ptr.")));
std::cout << *sptr4 << std::endl;
// 3.get()返回管理对象的指针
std::string* pstr = sptr1.get();
std::cout << "*pstr: " << *pstr << std::endl;
// 4.use_count()返回引用计数
std::cout << "sptr1.use_count: " << sptr1.use_count() << std::endl;
std::cout << "sptr2.use_count: " << sptr2.use_count() << std::endl;
std::cout << "sptr3.use_count: " << sptr3.use_count() << std::endl;
// 5.unique()检查是否仅仅被该对象关联
std::cout << "sptr1.unique: " << sptr1.unique() << std::endl;
std::cout << "sptr3.unique: " << sptr3.unique() << std::endl;
// 6.operator bool检查是否有关联的对象
if(sptr1)
std::cout << *sptr1 << std::endl;
// 7.reset()替换管理对象
sptr1.reset(new std::string("I'm new shared_ptr."));
std::cout << *sptr1 << std::endl;
// 8.swap()交换管理对象
sptr1.swap(sptr3);
std::cout << *sptr1 << std::endl;
// 9.使用std::make_shared(T&& t)创建shared_ptr.
auto sptr5 = std::make_shared<std::string>("I'm from std::makeA_shared.");
std::cout << *sptr5 << std::endl;
return 0;
/*
@ubuntu:~$ g++ shrptr.cpp -o shrptr --std=c++11
@ubuntu:~$ ./shrptr
I'm unique_ptr.
*pstr: I'm shared_ptr.
sptr1.use_count: 2
sptr2.use_count: 2
sptr3.use_count: 1
sptr1.unique: 0
sptr3.unique: 1
I'm shared_ptr.
I'm new shared_ptr.
Zhangsan
I'm from std::makeA_shared.
*/
weak_ptr
weak_ptr是一种不控制所管理对象生存期的智能指针,也就是说,它对被管理对象具有弱引用性。它指向一个由shared_ptr管理的对象,并且将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数,一旦最后指向被管理对象的shared_ptr被销毁,即使有weak_ptr指向对象,对象依旧被销毁释放。weak_ptr在访问所引用的对象前必须先转换为std::shared_ptr。
构造方法
weak_ptr构造函数如下:
constexpr weak_ptr() noexcept;
template<class Y>
weak_ptr(shared_ptr<Y> const& r) noexcept;
weak_ptr(weak_ptr const& r) noexcept;
template<class Y>
weak_ptr(weak_ptr<Y> const& r) noexcept;
weak_ptr(weak_ptr&& r) noexcept; // C++14
template<class Y>
weak_ptr(weak_ptr<Y>&& r) noexcept; // C++14
成员函数
- use_count():返回weak_ptr所指向对象的引用计数;
- expired():检查``weak_ptr是否过期,若被管理对象已被删除则为true,否则为false,等价于use_count() = 0`;
- lock():创建新的shared_ptr对象,它共享被管理对象的所有权。若无被管理对象,即 *this 为空,则返回亦为空的shared_ptr.
- reset():释放被管理对象的所有权;
- swap(weak_ptr& r):交换*this与r的内容;
使用weak_ptr示例如下:
#include <iostream>
#include <memory>
#include <string>
class Person
private:
std::string name;
public:
Person(const std::string& str):name(str)
~Person()
void show() const
std::cout << "Name: " << name << std::endl;
;
int main()
// 1.创建weak_ptr管理Person*
std::weak_ptr<Person> wptr1;
std::shared_ptr<Person> sptr1(new Person("Zhangsan"));
wptr1 = sptr1;
std::weak_ptr<Person> wptr2(wptr1);
std::cout << sptr1.use_count() << std::endl;
// 2.访问所管理对象前,必须转换为shared_ptr.
// wptr1->show();//Error
std::shared_ptr<Person> sptr2(wptr1);
sptr2->show();
std::cout << "sptr2.use_count: " << sptr2.use_count() << std::endl;
// 3.查看wptr1所管理对象的引用计数
std::cout << "wptr1.use_count: " << wptr1.use_count() << std::endl;
// 4.检查是否过期
std::weak_ptr<Person> wptr3;
auto sptr2 = std::make_shared<Person>("Lisi");
wptr3 = sptr2;
std::cout << "wptr3.expired ? " << std::boolalpha << wptr3.expired();
std::cout << ", use count: " << wptr3.use_count() << std::endl;
std::cout << "wptr3.expired ? " << std::boolalpha << wptr3.expired() << std::endl;
// 5.创建新的shared_ptr共享被管理对象
std::cout << "wptr1.use count before lock(): " << wptr1.use_count() << std::endl;
auto p = wptr1.lock();
std::cout << "wptr2.use count after locked(): " << wptr1.use_count() << std::endl;
p->show();
// 6.交换被管理对象
auto sptr3 = std::make_shared<Person>("XiaoQiang");
std::weak_ptr<Person> wptr4(sptr3);
wptr1.swap(wptr4);
std::cout << "wptr4.use_count: " << wptr4.use_count() << std::endl;
// 7.释放wptr1
wptr1.reset();
std::cout << "wptr1.expired ? " << std::boolalpha << wptr1.expired() << std::endl;
return 0;
创建完成weak_ptr对象之后,使用之前,需要调用expired函数来判断是否过期,如果没有过期,才能调用函数rock来获取share_ptr对象进行操作。
std::shared_ptr<string> share_str1(new string("hello world."));
std::cout << "share_str1.use_count()=" << share_str1.use_count() <<std::endl; //1
std::weak_ptr<string> weak_str1(share_str1);
std::cout << "weak_str1.use_count()=" << weak_str1.use_count() <<std::endl;//1
std::weak_ptr<string> weak_str2(weak_str1);
std::cout << "weak_str2.use_count()=" << weak_str2.use_count() <<std::endl;//1
//检查是否过期,没有的话,取出std::shared_ptr<string>进行操作
if(!weak_str2.expired())
std::shared_ptr<string> share_str2 = weak_str2.lock();
std::cout << "share_str2 = " << *share_str2 <<std::endl;//hello world.
weak_ptr的使用场景
weak_ptr最常见的用法,是和shared_ptr搭配使用,当shared_ptr用于类的成员时,如果存在循环引用,则将无法释放内存,请看下面示例:
#include <iostream>
#include <memory>
class B;
class A
private:
std::shared_ptr<B> m_sptr;
public:
void set_sptr(std::shared_ptr<B>& sptr)
m_sptr = sptr;
~A() std::cout << "A deleted." << std::endl;
;
class B
private:
std::shared_ptr<A> m_sptr;
public:
void set_sptr(std::shared_ptr<A>& sptr)
m_sptr = sptr;
~B() std::cout << "B deleted." << std::endl;
;
int main()
A* a = new A;
B* b = new B;
std::shared_ptr<A> asptr(a);
std::shared_ptr<B> bsptr(b);
std::cout << "apstr.use_count: " << asptr.use_count() << std::endl;
std::cout << "bsptr.use_count: " << bsptr.use_count() << std::endl;
a->set_sptr(bsptr);
b->set_sptr(asptr);
std::cout << "asptr.use_count: " << asptr.use_count() << std::endl;
std::cout << "bsptr.use_count: " << bsptr.use_count() << std::endl;
return 0;
/*
@ubuntu:~$ g++ shaptr.cpp -o shaptr --std=c++11
@ubuntu:~$ ./shaptr
apstr.use_count: 1
bsptr.use_count: 1
asptr.use_count: 2
bsptr.use_count: 2
*/
在以上示例中,A,B类分别带有shared_ptr的成员,并且通过set_sptr(shared_ptr&)互相共享管理对象,那么当程序执行完毕后,asptr和bsptr将负责释放管理对象的内存,但此时由于引用计数为2,因此减1,从而导致new申请的A和B的内存没有释放,从运行结果来看,他们的析构函数未执行。
为避免这种问题,只需要将shared_ptr成员修改为weak_ptr即可,因为weak_ptr对管理对象具有弱引用性,并且将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。修改后运行结果如下:
@ubuntu:~$ ./shaptr
apstr.use_count: 1
bsptr.use_count: 1
asptr.use_count: 1
bsptr.use_count: 1
B deleted.
A deleted.
auto_ptr(c++98的方案,cpp11已经抛弃)
采用所有权模式。
auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错.
此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!
以上是关于[C++]智能指针unique_ptr,shared_ptr,weak_ptr的主要内容,如果未能解决你的问题,请参考以下文章
[C++]智能指针unique_ptr,shared_ptr,weak_ptr