前言
智能指针的作用是管理一个指针。申请的空间在函数结束时忘记释放,就会造成内存泄漏。使用智能指针可以很大程度上避免这个问题,因为智能指针是一个类,当超出类的作用域时,类会自动调用析构函数来释放资源。所以智能指针的原理是在函数结束时自动释放内存空间,而不需要手动释放内存空间。
1. unique_ptr
1.1 独享管理对象所有权
unique_ptr独享被管理对象指针的所有权。
unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
一个错误的例子(不允许直接复制):
std::unique_ptr<string> p1(new string("c++"));
std::unique_ptr<string> p2;
p2 = p1; //error
一个正确的例子:
class A {
public:
A(int id) :mId(id) {
cout << "A::A" << endl;
}
~A() {
cout << "~A::A" << endl;
}
int getId() const
{
return mId;
}
private:
int mId;
};
int main()
{
{
std::unique_ptr<A> pA(new A(1));
cout << pA->getId() << endl;
}
system("pause");
return 0;
}
打印如下:
std::unique<A>的对象pA接收原始指针作为参数。当出作用域(正常退出或异常退出)时,在pA的析构函数中,调用类A(原始指针)的析构函数,删除关联的原始指针,这样就不用手动删除原始指针了。
1.2 创建对象——make_unique
auto pA = std::make_unique<A>(1);
1.3 获取被管理对象的原始指针
A* a1 = pA.get();
1.4 重置unique_ptr对象
调用reset()函数,将释放相关联的原始指针并使unique_ptr对象为空
std::unique_ptr<A> pA = std::make_unique<A>(1);
//A* a1 = pA.get();
pA.reset();
if (nullptr == pA)
{
cout << "pA is NULL" << endl;
}
1.5 转移unique_ptr的控制权
std::unique_ptr<A> pA = std::make_unique<A>(1);
std::unique_ptr<A> pB = std::move(pA);
if (nullptr == pA)
cout << "pA is NULL" << endl;
if(nullptr != pB)
cout << "pB is not NULL" << endl;
cout << "pB->getId() is " << pB->getId() << endl;
利用std::move将pA的控制权转移给pB,转移后pA的指针为空。
1.6 释放关联的原始指针
在 unique_ptr 对象上调用 release()
将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()
会delete原始指针。
std::unique_ptr<A> pA = std::make_unique<A>(1);
A* a1 = pA.release();
if (nullptr == pA)
{
cout << "pA is NULL" << endl;
}
2. shared_ptr
shared_ptr是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象。利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个shared_ptr共同管理同一个对象。
每个shared_ptr对象在内部指向两个内存位置:
1)指向对象的指针;
2)用于控制引用计数数据的指针;
共享所有权如何在参考计数的帮助下工作:
1)当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
2)当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete
函数删除该内存。
2.1 使用原始指针创建shared_ptr对象
方法1:
std::shared_ptr<int> p1(new int());
cout << "p1.use_count() = " << p1.use_count() << endl; //1
方法2:
auto p1 = std::make_shared<int>();
cout << "p1.use_count() = " << p1.use_count() << endl; //1
std::make_shared 一次性为int对象和用于引用计数的数据都分配了内存,而new操作符只是为int分配了内存
2.2 分离原始指针
auto p1 = std::make_shared<int>();
cout << "p1.use_count() = " << p1.use_count() << endl; //1
p1.reset();
cout << "p1.use_count() = " << p1.use_count() << endl; //0
执行reset后引用计数会减一,如果引用计数为0,则删除指针。
auto p1 = std::make_shared<int>();
cout << "p1.use_count() = " << p1.use_count() << endl; //1
p1.reset(new int(1));
cout << "p1.use_count() = " << p1.use_count() << endl; //1
p1 = nullptr;
cout << "p1.use_count() = " << p1.use_count() << endl; //0
这种情况下,它将在内部指向新指针,因此其引用计数将再次变为1。p1置为nullptr,引用计数变为0。
2.3 自定义删除器Deleter
1)回调函数
shared_ptr的析构函数中删除内部原始指针,默认调用的delete()函数。当我们需要析构delete[]时,需要自己设计回调函数删除。
class A
{
public:
A() {
cout << "A" << endl;;
}
~A() {
cout << "~A" << endl;;
}
};
void deleter(A *p)
{
cout << "DELETER FUNCTION CALLED" << endl;
delete[] p;
}
int main(){
std::shared_ptr<A> pA(new A[3], deleter);
}
打印结果:
2)使用函数对象作为删除器
class Deleter
{
public:
void operator() (A *p) {
std::cout << "DELETER FUNCTION CALLED\n";
delete[] p;
}
};
int main()
{
std::shared_ptr<A> p3(new A[3], Deleter());
}
3)使用lamba作为删除器(该方式较简洁)
std::shared_ptr<A> p4(new A[3], [](A * x) {
std::cout << "DELETER FUNCTION CALLED\n";
delete[] x;
});
2.4 NULL检测
不分配值的情况下默认为nullptr。
std::shared_ptr<int> p1;
if (nullptr == p1)
{
cout << "p1 is NULL" << endl;
}
2.5 常见问题
2.5.1 不要使用同一个原始指针构造 shared_ptr
int *num = new int(10);
std::shared_ptr<int> p1(num);
std::shared_ptr<int> p2(p1); // 正确使用方法
std::shared_ptr<int> p3(num); // 不推荐
std::cout << "p1 Reference = " << p1.use_count() << std::endl; // 输出 2
std::cout << "p2 Reference = " << p2.use_count() << std::endl; // 输出 2
std::cout << "p3 Reference = " << p3.use_count() << std::endl; // 输出 1
上面的代码实际测试会崩溃。原因在于,使用原始指针num
创建了p1,又同样方法创建了p3,当p1超出作用域时会调用delete
释放num
内存,此时num成了悬空指针,当p3超出作用域再次delete
的时候就可能会出错。
2.5.2 不要用栈中的指针构造 shared_ptr 对象
int x = 12;
std::shared_ptr<int> ptr(&x);
上面的代码在vs环境下运行会崩溃。原因在于,shared_ptr 默认的构造函数中使用的是delete
来删除关联的指针,所以构造的时候也必须使用new
出来的堆空间的指针。
2.5.3 建议使用make_shared()
auto p1 = make_shared<int>();
std::shared_ptr<int> p2(p1);
3. weak_ptr
3.1 一个循环引用的例子
class ClassB;
class ClassA
{
public:
ClassA() { cout << "ClassA Constructor..." << endl; }
~ClassA() { cout << "ClassA Destructor..." << endl; }
shared_ptr<ClassB> pb; // 在A中引用B
};
class ClassB
{
public:
ClassB() { cout << "ClassB Constructor..." << endl; }
~ClassB() { cout << "ClassB Destructor..." << endl; }
shared_ptr<ClassA> pa; // 在B中引用A
};
int main()
{
{
shared_ptr<ClassA> spa = make_shared<ClassA>();
shared_ptr<ClassB> spb = make_shared<ClassB>();
spa->pb = spb;
spb->pa = spa;
}
system("pause");
return 0;
}
打印结果:
3.2 weak_ptr的概念
weak_ptr是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。不论是否有weak_ptr指向,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放
3.3 weak_ptr创建
std::shared_ptr<int> sp(new int(5));
cout << "创建前sp的引用计数:" << sp.use_count() << endl; // 1
std::weak_ptr<int> wp(sp);
cout << "创建后sp的引用计数:" << sp.use_count() << endl; // 1
3.4 判断weak_ptr指向对象是否存在
C++11中通过lock()函数判断weak_ptr指向的对象是否存在。如果对象存在,lock()函数返回一个指向共享对象的shared_ptr,否则返回一个空shared_ptr。
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
{
std::shared_ptr<A> sp(new A());
std::weak_ptr<A> wp(sp);
sp.reset();
std::shared_ptr<A> p1 = wp.lock();
if (nullptr == p1)
{
cout << "wp指向的对象为空" << endl;
}
}
system("pause");
return 0;
}
3.5 weak_ptr的使用
class ClassB;
class ClassA
{
public:
ClassA() { cout << "ClassA Constructor..." << endl; }
~ClassA() { cout << "ClassA Destructor..." << endl; }
std::weak_ptr<ClassB> pb; // 在A中引用B
};
class ClassB
{
public:
ClassB() { cout << "ClassB Constructor..." << endl; }
~ClassB() { cout << "ClassB Destructor..." << endl; }
std::weak_ptr<ClassA> pa; // 在B中引用A
};
int main()
{
{
shared_ptr<ClassA> spa = make_shared<ClassA>();
shared_ptr<ClassB> spb = make_shared<ClassB>();
spa->pb = spb;
spb->pa = spa;
}
system("pause");
return 0;
}
打印结果: