上一篇:《深入理解C++11》笔记-强类型枚举
本篇介绍C++11中新增的智能指针:unique_ptr、shared_ptr、weak_ptr。使用智能指针可以免于我们去主动管理内存,智能指针会自动释放内存:
class Example {
public:
Example() { std::cout << "Example()" << std::endl; }
~Example() { std::cout << "~Example()" << std::endl; }
};
int main()
{
{
std::unique_ptr<Example> unique_p1(new Example()); // Example()
} // ~Example()
{
std::shared_ptr<Example> shared_p1(new Example()); // Example()
} // ~Example()
return 0;
}
在智能指针的作用域结束时,会自动释放申请的内存。
unique_ptr
从命名上看,unique_ptr就是独有指针,这里的独有代表什么?我们来看一下例子:
std::unique_ptr<int> p1(new int(0)); // 指向int类型的智能指针
std::unique_ptr<int> p2 = p1; // 编译失败,unique_ptr不能与其他指针共享同一份内存
std::shared_ptr<int> p3 = p1; // 编译失败,unique_ptr不能与其他指针共享同一份内存
std::unique_ptr<int> p4 = std::move(p1); // 使用move函数,能够转移内存的所有权
std::shared_ptr<int> p5 = std::move(p4);
从上面的代码可以看到,unique_ptr独占了指向的内存空间,其他智能指针不能与之共享。当释放引用或离开作用域时,内存就会被释放:
class Example {
public:
Example() { std::cout << "Example()" << std::endl; }
~Example() { std::cout << "~Example()" << std::endl; }
};
int main()
{
std::unique_ptr<Example> p1(new Example()); // Example()
p1.reset(); // ~Example(),释放引用,释放内存
return 0;
}
shared_ptr
shared_ptr和unique_ptr不同,它运行多个shared_ptr共享一份内存:
std::shared_ptr<int> p1(new int(0)); // 指向int类型的智能指针
std::cout << p1.use_count() << std::endl; // 1
std::shared_ptr<int> p2 = p1; // 引用计数增加
std::cout << p1.use_count() << std::endl; // 2
shared_ptr采用了引用计数的方式来计算有几个shared_ptr拥有该内存,通过use_count成员函数能够拿到引用计数总数。当引用计数为0,或则离开作用域时,内存就会被释放:
class Example {
public:
Example() { std::cout << "Example()" << std::endl; }
~Example() { std::cout << "~Example()" << std::endl; }
};
int main()
{
std::shared_ptr<Example> p1(new Example()); // Example()
std::cout << p1.use_count() << std::endl; // 1
std::shared_ptr<Example> p2 = p1;
std::cout << p1.use_count() << std::endl; // 2
p2.reset(); // 释放引用计数
std::cout << p1.use_count() << std::endl; // 1
p1.reset(); // ~Example(),释放引用计数,计数为0,释放内存
std::cout << p1.use_count() << std::endl; // 0
return 0;
}
weak_ptr
weak_ptr不能直接指向内存空间,只能指向shared_ptr:
class Example {
public:
Example() { std::cout << "Example()" << std::endl; }
~Example() { std::cout << "~Example()" << std::endl; }
};
int main()
{
std::weak_ptr<Example> weak_p1(new Example()); // 不能直接指向内存空间
std::unique_ptr<Example> unique_p1(new Example());
std::weak_ptr<Example> weak_p2 = unique_p1; // 不能指向unique_ptr
std::shared_ptr<Example> shared_p1(new Example());
std::weak_ptr<Example> weak_p3 = shared_p1; // 不占用引用计数
std::cout << shared_p1.use_count() << std::endl; // 1
std::shared_ptr<Example> shared_p2 = shared_p1;
std::cout << weak_p3.use_count() << std::endl; // 2,同样能获取引用计数
std::shared_ptr<Example> shared_p3 = weak_p3.lock(); // lock返回shared_ptr对象
std::cout << shared_p1.use_count() << std::endl; // 3
shared_p1.reset();
shared_p2.reset();
shared_p3.reset();
if (!weak_p3.lock()) // 智能指针已经释放,weak_ptr.lock返回空指针
{
std::cout << "nullptr" << std::endl;
}
return 0;
}
weak_ptr指向shared_ptr对象,但是本身不占用引用计数;weak_ptr中的lock成员函数能够对shared_ptr智能指针进行有效性判断,防止空指针引用。
另外,weak_ptr还能解决shared_ptr的循环引用问题,来看一个双向链表的例子:
template<typename T>
struct ListNode{
T _value;
std::shared_ptr<ListNode> _prev;
std::shared_ptr<ListNode> _next;
ListNode(const T & value)
:_value(value),_prev(nullptr),_next(nullptr){}
~ListNode(){
std::cout << "~ListNode()" << std::endl;
}
};
void func()
{
std::shared_ptr<ListNode<int>> a(new ListNode<int>(1));
std::shared_ptr<ListNode<int>> b(new ListNode<int>(2));
std::cout<<a.use_count()<<std::endl; // 1
std::cout<<b.use_count()<<std::endl; // 1
{
a->_next = b;
b->_prev = a;
}
std::cout<<a.use_count()<<std::endl; // 2
std::cout<<b.use_count()<<std::endl; // 2
}
int main()
{
func();
return 0;
}
如上代码,在a和b构成了一个只有两个node的循环链表的时候,a和b互相引用了对方。可以看到,在创建a和b之后,它们的引用计数都是1;相互引用之后,即使出了作用域,引用计数还是2;并且退出函数之后,a和b并没有被析构,因为此时两者的引用计数是1,没有变为0。
这个时候,weak_ptr就有用了,把模板中的shared_ptr换成weak_ptr就不会有这个问题了,因为它不增加引用计数。
template<typename T>
struct ListNode{
T _value;
std::weak_ptr<ListNode> _prev;
std::weak_ptr<ListNode> _next;
ListNode(const T & value)
:_value(value){}
~ListNode(){
std::cout<<"~ListNode()"<<std::endl;
}
};
指针对象使用
现在我们了解了unique_ptr、shared_ptr、weak_ptr,再看看他们指向的对象应该怎么使用:
std::unique_ptr<int> unique_p1(new int(0));
std::cout << *unique_p1 << std::endl; // 直接使用指针对象
std::cout << *unique_p1.get() << std::endl; // 通过get获取int*
std::shared_ptr<int> shared_p1(new int(0));
std::cout << *shared_p1 << std::endl; // 直接使用指针对象
std::cout << *shared_p1.get() << std::endl; // 通过get获取int*
std::weak_ptr<int> weak_p1 = shared_p1;
std::cout << *weak_p1 << std::endl; // 不能直接使用
std::cout << *weak_p1.get() << std::endl; // 不能通过get获取int*
std::cout << *weak_p1.lock() << std::endl; // 通过lock获取shared_ptr对象,再使用对象
std::cout << *weak_p1.lock().get() << std::endl; // 通过lock获取shared_ptr对象,再使用get获取int*