需要包含下面两句代码:
#include
using namespace std;
shared_ptr是一种 智能指针(smart pointer),作用有如同 指针,但会记录有多少个shared_ptrs共同指向一个对象。这便是所谓的 引用计数(reference counting)。一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。使得指针可以共享对象,并且不用考虑内存泄漏问题
shared_ptr 可以支持普通指针的所有操作,完全可以像操作普通指针一样操作智能指针。
shared_ptr 可以通过三种方式得到(拷贝初始化,定义delete操作的方式不在罗列,只讨论初始化指针所指对象来源):
#include
#include
int main()
{
int *p = new int(30);
std::shared_ptr<int> bptr(p);//方式1
std::shared_ptr<int> aptr = std::make_shared<int>(20);//方式2
std::shared_ptr<int> cptr(aptr); //方式3
std::cout << "aptr.use_count() = " << aptr.use_count() <<" value = "<<*aptr<<std::endl;//use_count 是引用计数器
std::cout << "bptr.use_count() = " << bptr.use_count() <<" value = "<<*bptr<<std::endl;
std::cout << "cptr.use_count() = " << cptr.use_count() <<" value = "<<*cptr<<std::endl;
//输出是:2,20
// 1,30
// 2,20
}
另外还有这种初始化方式:
std::shared_ptr<int> ptr;
ptr.reset(new int(1));
对于一个未初始化的智能指针,可以用过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减1.
当需要获取原始指针时,可以通过get方法,代码如下:
std::shared_ptr<int> ptr(new int(1));
int *p = ptr.get();
void DeleteIntPtr(int *p)
{
delete p;
}
shared_ptr<int> ppp(new int(100), DeleteIntPtr);
当ppp的引用计数为0时,自动调用DeleteIntPtr来释放对象的内存。删除器也可以是一个lambal表达式,如下:
shared_ptr<int> ppp(new int(100), [](int*p){delete p;});
注意:当我们用shared_ptr管理数组时,需要指定删除器,因为std::shared_ptr的默认删除器不支持数组对象,代码如下:
shared_ptr<int> ppp(new int[10], [](int*p){delete[] p;}); //指定delete[]
也可以将std::default_delete作为删除器 。default_delete的内部是通过调用delete来实现功能的,代码如下:
shared_ptr<int> ppp(new int[10], std::default_delete<int []>);
另外,我们还可以自己封装一个 make_shared_array 方法来让 shared_ptr 支持数组,代码如下:
#include
#include `在这里插入代码片`
using namespace std;
template <typename T>
shared_ptr<T> make_share_array(size_t size)
{
// 返回匿名对象
return shared_ptr<T>(new T[size], default_delete<T[]>());
}
int main()
{
shared_ptr<int> ptr1 = make_share_array<int>(10);
cout << ptr1.use_count() << endl;
shared_ptr<char> ptr2 = make_share_array<char>(128);
cout << ptr2.use_count() << endl;
return 0;
}
不过在C++ 14 以后,shared_ptr支持了可以管理数组类型的地址了。
代码如下:
int main()
{
shared_ptr<Test[]> ptr4(new Test[3]);
return 0;
}
int* a=new int(2);
shared_ptr<int>sp=a;// error
sp=a;// error
2. shared_ptr多次引用同一数据,会导致两次释放同一内存。如下:
{
int* pInt = new int[100];
shared_ptr<int> sp1(pInt);
// 一些其它代码之后…
shared_ptr<int> sp2(pInt);
}
3.使用shared_ptr包装this指针带来的问题,如下:
class tester
{
public:
tester()
~tester()
{
std::cout << "析构函数被调用!\n";
}
public:
shared_ptr<tester> sget()
{
return shared_ptr<tester>(this);
}
};
int main()
{
tester t;
shared_ptr<tester> sp = t.sget(); // …
return 0;
}
也将导致两次释放t对象破坏堆栈,一次是出栈时析构,一次就是shared_ptr析构。若有这种需要,可以使用下面代码。
class tester : public enable_shared_from_this<tester>
{
public:
tester()
~tester()
{
std::cout << "析构函数被调用!\n";
}
public:
shared_ptr<tester> sget()
{
return shared_from_this();
}
};
int main()
{
shared_ptr<tester> sp(new tester);
// 正确使用sp 指针。
sp->sget();
return 0;
}
4. shared_ptr循环引用导致内存泄露,代码如下:
class parent;
class child;
typedef shared_ptr<parent> parent_ptr;
typedef shared_ptr<child> child_ptr;
class parent
{
public:
~parent() {
std::cout <<"父类析构函数被调用.\n";
}
public:
child_ptr children;
};
class child
{
public:
~child() {
std::cout <<"子类析构函数被调用.\n";
}
public:
parent_ptr parent;
};
int main()
{
parent_ptr father(new parent());
child_ptr son(new child);
// 父子互相引用。
father->children = son;
son->parent = father;
return 0;
}
如上代码,将在程序退出前,father的引用计数为2,son的计数也为2,退出时,shared_ptr所作操作就是简单的将计数减1,如果为0则释放,显然,这个情况下,引用计数不为0,于是造成father和son所指向的内存得不到释放,导致内存泄露。
5.在多线程程序中使用shared_ptr应注意的问题。代码如下:
class tester
{
public:
tester() {}
~tester() {}
// 更多的函数定义…
};
void fun(shared_ptr<tester> sp)
{
// !!!在这大量使用sp指针.
shared_ptr<tester> tmp = sp;
}
int main()
{
shared_ptr<tester> sp1(new tester);
// 开启两个线程,并将智能指针传入使用。
thread t1(bind(&fun, sp1));
thread t2(bind(&fun, sp1));
t1.join();
t2.join();
return 0;
}
这个代码带来的问题很显然,由于多线程同时访问智能指针,并将其赋值到其它同类智能指针时,很可能发生两个线程同时在操作引用计数(但并不一定绝对发生),而导致计数失败或无效等情况,从而导致程序崩溃,如若不知根源,就无法查找这个bug,那就只能向上帝祈祷程序能正常运行。
引入weak_ptr可以解决这个问题,将fun函数修改如下:
void fun(weak_ptr<tester> wp)
{
//这个方案只解决了多线程对引用计数同时访问的读写问题,并没有解决对share_ptr指向的数据的多线程安全问题,因此weak_ptr只是安全的获得share_ptr的一种方式,因为可以确保在获得share_ptr的时候的多线程安全
shared_ptr<tester> sp = wp.lock;
if (sp)
{
// 在这里可以安全的使用sp指针.
}
else
{
std::cout << “指针已被释放!” << std::endl;
}
}
6.weak_ptr不仅可以解决多线程访问带来的安全问题,而且还可以解决上面第三个问题循环引用。Children类代码修改如下,即可打破循环引用:
class child
{
public:
~child() {
std::cout <<"子类析构函数被调用.\n";
}
public:
weak_ptr<parent> parent;
};
因为weak_ptr不增加引用计数,所以可以在退出函数域时,正确的析构。
这段原文链接:https://blog.csdn.net/shaosunrise/article/details/85158249
unique_ptr 是 C++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享被管理对象指针所有权的智能指针。unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。
查看下面的示例:
#include
#include
struct Task {
int mId;
Task(int id ) :mId(id) {
std::cout << "Task::Constructor" << std::endl;
}
~Task() {
std::cout << "Task::Destructor" << std::endl;
}
};
int main()
{
// 通过原始指针创建 unique_ptr 实例
std::unique_ptr<Task> taskPtr(new Task(23));
//通过 unique_ptr 访问其成员
int id = taskPtr->mId;
std::cout << id << std::endl;
return 0;
}
输出:
Task::Constructor
23
Task::Destructor
unique_ptr 对象 taskPtr 接受原始指针作为参数。现在当main函数退出时,该对象超出作用范围就会调用其析构函数,在unique_ptr对象taskPtr 的析构函数中,会删除关联的原始指针,这样就不用专门delete Task对象了。
这样不管函数正常退出还是异常退出(由于某些异常),也会始终调用taskPtr的析构函数。因此,原始指针将始终被删除并防止内存泄漏。
unique_ptr对象始终是关联的原始指针的唯一所有者。我们无法复制unique_ptr对象,它只能移动。
由于每个unique_ptr对象都是原始指针的唯一所有者,因此在其析构函数中它直接删除关联的指针,不需要任何参考计数。
创建一个空的unique_ptr对象,因为没有与之关联的原始指针,所以它是空的。
std::unique_ptr<int> ptr1;
有两种方法可以检查 unique_ptr 对象是否为空或者是否有与之关联的原始指针。
// 方法1
if(!ptr1)
std::cout<<"ptr1 is empty"<<std::endl;
// 方法2
if(ptr1 == nullptr)
std::cout<<"ptr1 is empty"<<std::endl;
要创建非空的 unique_ptr 对象,需要在创建对象时在其构造函数中传递原始指针,即:
std::unique_ptr<Task> taskPtr(new Task(22));
或者(这种新学到的)
std::unique_ptr<Task> taskPtr(new std::unique_ptr<Task>::element_type(23));
不能通过赋值的方法创建对象,下面的这句是错误的
// std::unique_ptr taskPtr2 = new Task(); // 编译错误
std::make_unique<>() 是C++ 14 引入的新函数
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);
使用get()·函数获取管理对象的指针。
Task *p1 = taskPtr.get();
在 unique_ptr 对象上调用reset()函数将重置它,即它将释放delete关联的原始指针并使unique_ptr 对象为空。
taskPtr.reset();
由于 unique_ptr 不可复制,只能移动。因此,我们无法通过复制构造函数或赋值运算符创建unique_ptr对象的副本。
// 编译错误 : unique_ptr 不能复制
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error
// 编译错误 : unique_ptr 不能复制
taskPtr = taskPtr2; //compile error
我们无法复制 unique_ptr 对象,但我们可以转移它们。这意味着 unique_ptr 对象可以将关联的原始指针的所有权转移到另一个 unique_ptr 对象。让我们通过一个例子来理解:
// 通过原始指针创建 taskPtr2
std::unique_ptr<Task> taskPtr2(new Task(55));
// 把taskPtr2中关联指针的所有权转移给taskPtr4
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 现在taskPtr2关联的指针为空
if(taskPtr2 == nullptr)
std::cout<<"taskPtr2 is empty"<<std::endl;
// taskPtr2关联指针的所有权现在转移到了taskPtr4中
if(taskPtr4 != nullptr)
std::cout<<"taskPtr4 is not empty"<<std::endl;
// 会输出55
std::cout<< taskPtr4->mId << std::endl;
std::move() 将把 taskPtr2 转换为一个右值引用。因此,调用 unique_ptr 的移动构造函数,并将关联的原始指针传输到 taskPtr4。在转移完原始指针的所有权后, taskPtr2将变为空。
在 unique_ptr 对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。
std::unique_ptr<Task> taskPtr5(new Task(55));
// 不为空
if(taskPtr5 != nullptr)
std::cout<<"taskPtr5 is not empty"<<std::endl;
// 释放关联指针的所有权
Task * ptr = taskPtr5.release();
// 现在为空
if(taskPtr5 == nullptr)
std::cout<<"taskPtr5 is empty"<<std::endl;
#include
#include
struct Task {
int mId;
Task(int id ) :mId(id) {
std::cout<<"Task::Constructor"<<std::endl;
}
~Task() {
std::cout<<"Task::Destructor"<<std::endl;
}
};
int main()
{
// 空对象 unique_ptr
std::unique_ptr<int> ptr1;
// 检查 ptr1 是否为空
if(!ptr1)
std::cout<<"ptr1 is empty"<<std::endl;
// 检查 ptr1 是否为空
if(ptr1 == nullptr)
std::cout<<"ptr1 is empty"<<std::endl;
// 不能通过赋值初始化unique_ptr
// std::unique_ptr taskPtr2 = new Task(); // Compile Error
// 通过原始指针创建 unique_ptr
std::unique_ptr<Task> taskPtr(new Task(23));
// 检查 taskPtr 是否为空
if(taskPtr != nullptr)
std::cout<<"taskPtr is not empty"<<std::endl;
// 访问 unique_ptr关联指针的成员
std::cout<<taskPtr->mId<<std::endl;
std::cout<<"Reset the taskPtr"<<std::endl;
// 重置 unique_ptr 为空,将删除关联的原始指针
taskPtr.reset();
// 检查是否为空 / 检查有没有关联的原始指针
if(taskPtr == nullptr)
std::cout<<"taskPtr is empty"<<std::endl;
// 通过原始指针创建 unique_ptr
std::unique_ptr<Task> taskPtr2(new Task(55));
if(taskPtr2 != nullptr)
std::cout<<"taskPtr2 is not empty"<<std::endl;
// unique_ptr 对象不能复制
//taskPtr = taskPtr2; //compile error
//std::unique_ptr taskPtr3 = taskPtr2;
{
// 转移所有权(把unique_ptr中的指针转移到另一个unique_ptr中)
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 转移后为空
if(taskPtr2 == nullptr)
std::cout << "taskPtr2 is empty" << std::endl;
// 转进来后非空
if(taskPtr4 != nullptr)
std::cout<<"taskPtr4 is not empty"<<std::endl;
std::cout << taskPtr4->mId << std::endl;
//taskPtr4 超出下面这个括号的作用于将delete其关联的指针
}
std::unique_ptr<Task> taskPtr5(new Task(66));
if(taskPtr5 != nullptr)
std::cout << "taskPtr5 is not empty" << std::endl;
// 释放所有权
Task * ptr = taskPtr5.release();
if(taskPtr5 == nullptr)
std::cout << "taskPtr5 is empty" << std::endl;
std::cout << ptr->mId << std::endl;
delete ptr;
return 0;
}
输出:
ptr1 is empty
ptr1 is empty
Task::Constructor
taskPtr is not empty
23
Reset the taskPtr
Task::Destructor
taskPtr is empty
Task::Constructor
taskPtr2 is not empty
taskPtr2 is empty
taskPtr4 is not empty
55
Task::Destructor
Task::Constructor
taskPtr5 is not empty
taskPtr5 is empty
66
Task::Destructor
new出来的对象是位于堆内存上的,必须调用delete才能释放其内存。
unique_ptr 是一个装指针的容器,且拥有关联指针的唯一所有权,作为普通变量使用时系统分配对象到栈内存上,超出作用域时会自动析构,unique_ptr对象的析构函数中会delete其关联指针,这样就相当于替我们执行了delete堆内存上的对象。
reset() 重置unique_ptr为空,delete其关联的指针。
release() 不delete关联指针,并返回关联指针。释放关联指针的所有权,unique_ptr为空。
get() 仅仅返回关联指针
unique_ptr不能直接复制,必须使用std::move()转移其管理的指针,转移后原 unique_ptr 为空。std::unique_ptr taskPtr4 = std::move(taskPtr2);
创建unique_ptr对象有两种方法:
//C++11:
std::unique_ptr<Task> taskPtr(new Task(23));
//C++14:
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);
和 shared_ptr、unique_ptr 类型指针一样,weak_ptr 智能指针也是以模板类的方式实现的。weak_ptr( T 为指针所指数据的类型)定义在头文件,并位于 std 命名空间中。因此,要想使用 weak_ptr 类型指针,程序中应首先包含如下 2 条语句:
#include
using namespace std;
第 2 句并不是必须的,可以不添加,则后续在使用 unique_ptr 指针时,必须标注std::。
需要注意的是,C++11标准虽然将 weak_ptr 定位为智能指针的一种,但该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用。甚至于,我们可以将 weak_ptr 类型指针视为 shared_ptr 指针的一种辅助工具,借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。
需要注意的是,当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数。
除此之外,weak_ptr 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。
创建一个 weak_ptr 指针,有以下 3 种方式:
std::weak_ptr<int> wp1;
std::weak_ptr<int> wp2 (wp1);
若 wp1 为空指针,则 wp2 也为空指针;反之,如果 wp1 指向某一 shared_ptr 指针拥有的堆内存,则 wp2 也指向该块存储空间(可以访问,但无所有权)。
std::shared_ptr<int> sp (new int);
std::weak_ptr<int> wp3 (sp);
由此,wp3 指针和 sp 指针有相同的指针。再次强调,weak_ptr 类型指针不会导致堆内存空间的引用计数增加或减少。
和 shared_ptr、unique_ptr 相比,weak_ptr 模板类提供的成员方法不多,罗列了常用的成员方法及各自的功能。
成员方法 功能
operator=() 重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。
swap(x) 其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。
reset() 将当前 weak_ptr 指针置为空指针。
use_count() 查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。
expired() 判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)。
lock() 如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。
再次强调,weak_ptr 模板类没有重载 * 和 -> 运算符,因此 weak_ptr 类型指针只能访问某一 shared_ptr 指针指向的堆内存空间,无法对其进行修改。
下面的样例演示了 weak_ptr 指针以及表 1 中部分成员方法的基本用法:
#include
#include
using namespace std;
int main()
{
std::shared_ptr<int> sp1(new int(10));
std::shared_ptr<int> sp2(sp1);
std::weak_ptr<int> wp(sp2);
//输出和 wp 同指向的 shared_ptr 类型指针的数量
cout << wp.use_count() << endl;
//释放 sp2
sp2.reset();
cout << wp.use_count() << endl;
//借助 lock() 函数,返回一个和 wp 同指向的 shared_ptr 类型指针,获取其存储的数据
cout << *(wp.lock()) << endl;
return 0;
}
程序执行结果为:
2
1
10