通用工具——智能指针
自C++11起,C++标准库提供两大类型的智能指针:
- shared_ptr实现共享式拥有。多个shared_ptr可以指向同一对象,该对象在最后一个引用被销毁时释放。标准库还提供了weak_ptr、bad_weak_ptr和enable_shared_fron_this等辅助类。
- unique_ptr实现独占式拥有。同一时间内只有一个unique_ptr可以指向该对象,在引用计数为0时释放该对象所占的资源。
C++98提供的auto_ptr实现了跟unique_ptr类似的功能,现在已经被废弃(除非为了兼容陈旧的代码);shared_ptr在TR1中被首次提出;unique_ptr在C++11中被引入。所有的智能指针都被定义于头文件<memory>内。
1.shared_ptr
// shared_ptr的初始化和赋值操作.
std::shared_ptr<std::string> pNico(new std::string("nico"));
std::shared_ptr<std::string> pJack = std::make_shared<std::string>("jack");
std::shared_ptr<std::string> pMike;
pMike.reset(new std::string("mike"));
(*pNico)[0] = 'N'; // 修改元素.
long cnt = pJack.use_count(); // 引用计数.
pMike = pJack;
cnt = pMike.use_count(); // 引用计数.
std::shared_ptr<std::string> pKeke(new std::string("keke"), // 自定义清除函数.
[](std::string* p) {std::cout << "delete " << *p << std::endl; delete p; });
pKeke = nullptr;
std::shared_ptr<double> pKekeVec(new double[10], // 自定义清除函数.
[](double* p) {std::cout << "delete double vector" << std::endl; delete[] p; });
pKekeVec = nullptr;
std::shared_ptr<double> pKekeVec2(new double[10], // 默认清除函数.
std::default_delete<double[]>());
pKekeVec2 = nullptr;
std::shared_ptr<int> pInt(new int(100));
*pInt = 99;
*pInt.get() = 88;
pInt.get()[0] = 66;
(&*pInt)[0] = 55;
// std::get_deleter的使用方法.
auto del = [](int * p) {delete p; };
std::shared_ptr<int> pSharedPtr(new int(10), del);
decltype(del)* pdel = std::get_deleter<decltype(del)>(pSharedPtr);
// shared_ptr引用不会增加引用计数.
std::shared_ptr<int>& pInt2 = pInt;
cnt = pInt2.use_count();
pInt2 = nullptr;
// 循环引用,shared_ptr指向的内存无法释放【shared_ptr常见误区之一,解决办法见下一小节】.
struct Node
{
int data;
std::shared_ptr<Node> pre;
std::shared_ptr<Node> next;
};
std::shared_ptr<Node> Node1(new Node{100,nullptr,nullptr});
std::shared_ptr<Node> Node2(new Node{101,nullptr,nullptr});
Node1->next = Node2;
Node2->pre = Node1;
Node1 = nullptr;
std::cout << Node2->pre.get()->data << std::endl;
std::cout << "Node1.use_count:" << Node1.use_count() << std::endl; // 引用计数为0,而实际堆空间依然未释放.
std::cout << "Node2.use_count:" << Node2.use_count() << std::endl; // 引用计数不为0.
// 指针存在但内存已被销毁的悬挂指针【shared_ptr常见误区之二】.
int* pNum = new int(10);
std::shared_ptr<int> sp1(pNum); // error.
std::shared_ptr<int> sp2(pNum); // error.
long cnt1 = sp1.use_count(); // sp1引用计数为1.
long cnt2 = sp2.use_count(); // sp2引用计数为1.
sp1 = nullptr; // 以上写法将在运行结束自动释放内存时对pNum所指区域释放2次而报错.
sp2 = nullptr; // 编译程序将在此处中断.
std::shared_ptr<int> _sp1(new int(10)); // ok.
std::shared_ptr<int> _sp2(_sp1); // ok.
long cnt3 = _sp1.use_count(); // sp1引用计数为2.
long cnt4 = _sp2.use_count(); // sp2引用计数为2.
shared_ptr的具体操作见下表:
2.weak_ptr
weak_ptr允许你仅共享但不拥有某对象,即weak_ptr对象不会增加引用计数,其他行为与shared_ptr相同。此外,weak_ptr不支持*和->运算符,因为其实际内存是否存在需要使用“expired()或use_count()”检测,或者强转shared_ptr检测是否抛出bad_weak_ptr异常。具体用法如下:
// 使用weak_ptr解决shared_ptr循环引用问题.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using std::string;
using std::shared_ptr;
using std::weak_ptr;
using std::cout;
using std::endl;
using std::vector;
class Person
{
public:
string m_name;
shared_ptr<Person> m_mother;
shared_ptr<Person> m_father;
vector<weak_ptr<Person>> m_kids;// 此处用weak_ptr替换shared_ptr,以解决循环引用计数不为0的问题.
Person(const string& n,
const shared_ptr<Person>& m = nullptr,
const shared_ptr<Person>& f = nullptr)
: m_name(n), m_mother(m), m_father(f)
{
}
~Person()
{
cout << "delete" << m_name << endl;
}
};
shared_ptr<Person> initFamily(const string& name)
{
shared_ptr<Person> mom(new Person(name + "'s mother"),
[](Person* p) {std::cout << "delete mom" << std::endl; delete p; });
shared_ptr<Person> dad(new Person(name + "'s father"),
[](Person* p) {std::cout << "delete dad" << std::endl; delete p; });
shared_ptr<Person> kid(new Person(name, mom, dad),
[](Person* p) {std::cout << "delete kid" << std::endl; delete p; });
mom->m_kids.push_back(kid);
dad->m_kids.push_back(kid);
return kid;
}
int main()
{
shared_ptr<Person> pFamily = initFamily("Bob");
pFamily = nullptr;
return 0;
}
//输出结果:
//delete kid
//deleteBob
//delete dad
//deleteBob's father
//delete mom
//deleteBob's mother
weak_ptr的具体操作见下表:
3.enable_shared_from_this<>和shared_from_this()
enable_shared_from_this<>和shared_from_this()用于获取this指针指向的本对象的一个智能指针。其用法如下:
class Person : public std::enable_shared_from_this<Person>
{
public:
void setParentsAndTheirKids(shared_ptr<Person> m = nullptr, shared_ptr<Person> f = nullptr)
{
mother = m;
father = f;
if (m != nullptr)
{
m->kids.push_back(shared_from_this()); // OK(注意:shared_from_this()不能用在构造函数中).
}
if (f != nullptr)
{
f->kids.push_back(shared_from_this()); // OK.
}
}
}
4.unique_ptr
unique_ptr由C++11标准提供,实现了独占式拥有概念,意味着它可以确保一个对象和其相应资源同一时间只被一个指针拥有。一旦拥有者被销毁或变成空,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其相关资源会被释放。类unique_ptr继承自auto_ptr,auto_ptr由C++98引入,现在已不再使用。unique_ptr的使用与shared_ptr和weak_ptr类似,以下列出其不同的几点。
// unique_ptr多用于局域变量.
void func()
{
std::unique_ptr<double> pDbl(new double);
// ...
}
std::unique_ptr<std::string> up(new std::string("nico"));
up->release(); // 释放引用.
// 转移拥有权.
ClassA * pA = new ClassA;
std::unique_ptr<ClassA> up1(new ClassA);
std::unique_ptr<ClassA> up2(up1); // error.
std::unique_ptr<ClassA> up3(pA); // true.
std::unique_ptr<ClassA> up4(std::move(up3)); // true.
// 此处补充一个知识点:C++11规定,编译器应该自动尝试加上std::move().
// 数组的智能指针的定义.
std::unique_ptr<std::string> up0(new std::string[10]); // error.
std::unique_ptr<std::string[]> up0(new std::string[10]); // ok.
// 该偏特化版本提供的接口稍有不同,不支持*和->,改而提供[].
std::cout << *up0; // error.
std::cout << up0[3]; // ok.
unique_ptr的其他用法可参考shared_ptr,其各项操作如下所示:
auto_ptr现在已不再建议使用,对此不再对其介绍,智能指针的知识在此告一段落。