一、基础概念
谈到C ,绕不开的一个特性是智能指针,智能指针见字如面:有两个概念:一个是指针,一个是“智能”。
- 和类似指针的相同使用方式使用他,它可以托管任何使用“new”创建的对象。
- “智能”指的是使用者不需要关注什么时候是否需要删除这块new出来的内存,内存管理由指针内部自动删除
1.1 范畴
C 11智能指针的范畴包括
- 类型unique_ptr, shared_ptr和weak_ptr
- 配到的function和模板class
1.2 解决问题和实现机制
智能指针解决的问题是在它出现之前,object的拥有者释放问题。它利用了编译器一个基本机制:class的对象销毁时,析构函数destructor一定会被call到。
使用方式首先include头文件<memory>
代码语言:javascript复制#include <memory>
1.3 类型
1.3.1 shared_ptr
它是一种拥有引用计数的智能指针类型,当引用计数减为0时,真实内存自动被释放。shared指的是ownership拥有权被多个使用者share。比如说下图,A container和B container同时分享了对象X1,X2和X3的拥有权,当A container移除X1的拥有权时,我们需要检查X1内存是否能被释放,取决于X1的使用者是否为0,在这个例子中需要考虑Container B是否还有在使用X1.
1.3.2 weak_ptr
考虑到一种引用关系图:环形引用,如下图例子,3个sp智能指针互相引用,形成一个环形依赖,此时释放container,那么3个sp永远不会被释放,因为他们的被引用计数大于0。C 11引入了weak_ptr用来出来三个sp之间的相互引用,它用来表明引用关系,但是不用于管理对象内存上。
这里你仍然有个疑问,weak_ptr不能用普通的pointer替代,回答这个问题前,我们来挖掘看看weak_ptr到底还有其他的收益吗
以下这个例子显示了3个shared指针sp1,sp2和sp3拥有了一块对象,接着还有两个wp1和wp2也拥有了这块对象。当讨论到对象是否能被释放,我们遵循一个标准:shared_count是否为0决定了删除managed_object,weaked_ptr和shared_ptr是否同时为0决定删除manager_object。weak_ptr的作用就是在这里监听着是否还有managed object,如果还有,那么下一个新来的shared_ptr还可以继续使用。
二、实战:
2.1 shared_ptr的例子
2.1.1 使用方式一
使用shared_ptr<typename class> name(new object),因为C 是不断发展的,这里展示了一个C 17的进阶版本,可以用shared_ptr管理array
代码语言:javascript复制//需要支持使用c 17编译器
#include <memory>
#include <iostream>
struct Test {
Test() { std::cout << "Test::Testn"; }
~Test() { std::cout << "Test::~Test destructorn"; }
int val_ { 0 };
};
int main() {
std::shared_ptr<Test[]> ptr(new Test[3]);
std::cout << "finishing main...n";
}
C 20支持这种写法
代码语言:javascript复制auto ptr = std::make_shared<Test[]>(3);
2.1.2 使用方式二
使用std::make_shared,那么两种使用方式有区别吗,答案是sp和sp2的内存分配过程不一样,sp需要经历两次new过程:一次new Test,一次new 引用计数。而使用make_shared方式,两者内存的地址更接近,在系统底层上可能只需要向系统申请分配一次大内存块就行。
代码语言:javascript复制#include <memory>
#include <iostream>
struct Test {
Test() { std::cout << "Test::Testn"; }
~Test() { std::cout << "Test::~Test destructorn"; }
int val_ { 0 };
};
int main() {
std::shared_ptr<Test> sp(new Test());
std::shared_ptr<Test> sp2 = std::make_shared<Test>();
std::cout << "finishing main...n";
}
基本上99%场合优先使用maske_shared,那么这里的1%特例是什么场景?
这里有种weak_ptr和shared_ptr共同使用的例子,之前所说的weak_ptr保存着weak_counter,这个weak_counter和shared_pointer的计数地址相近,也就是说即使shared_pointer的shared_count为0,但是由于weak_count和shared_count的内存是同一块,这使得shared_count的内存得不到释放。
2.1.3 shared_ptr和多态
C 多态是重要功能,结合shared_ptr,也可以有多态
代码语言:javascript复制#include <memory>
#include <iostream>
class Base {
public:
Base() { std::cout << "Base::Basen"; }
~Base() { std::cout << "Base::~Base destructorn"; }
int val_ { 0 };
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived::Derivedn"; }
~Derived() { std::cout << "Derived::~Derived destructorn"; }
//int val_ { 0 };
};
int main() {
std::shared_ptr<Derived> dp1(new Derived); // 调用了移动构造函数,并读取了createTest的返回rvalue值
std::shared_ptr<Base> bp1 = dp1; // 多态例子1
std::shared_ptr<Base> bp2(dp1); // 多态例子2
std::shared_ptr<Base> bp3(new Derived); // 多态例子3
std::cout << "finishing main...n";
}
2.1.4类型转换
使用c 11提供的 static_pointer_cast、const_pointer_case和dynamic_pointer_cast进行基类->派生类的类型转换
代码语言:javascript复制#include <memory>
#include <iostream>
class Base {
public:
Base() { std::cout << "Base::Basen"; }
~Base() { std::cout << "Base::~Base destructorn"; }
int val_ { 0 };
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived::Derivedn"; }
~Derived() { std::cout << "Derived::~Derived destructorn"; }
//int val_ { 0 };
};
int main() {
std::shared_ptr<Derived> dp1(new Derived); // 调用了移动构造函数,并读取了createTest的返回rvalue值
std::shared_ptr<Base> bp1 = dp1;
std::shared_ptr<Derived> dp2;
dp2 = std::static_pointer_cast<Derived>(bp1); // 类型转换
std::cout << "finishing main...n";
}
2.2 weak_ptr
weak_ptr不能使用operator*和operator->,而且也没有get()函数。weak_ptr一般和shared_ptr配套使用。
2.2.1 weak_ptr的初始化和赋值
weak_ptr默认是空的,不指到任何对象。它可以接受shared_ptr或者另一个weak_ptr的赋值。
代码语言:javascript复制#include <memory>
#include <iostream>
struct Test {
Test() { std::cout << "Test::Testn"; }
~Test() { std::cout << "Test::~Test destructorn"; }
int val_ { 0 };
};
int main() {
std::shared_ptr<Test> sp(new Test());
std::weak_ptr<Test> wp1(sp); // construct wp1 from a shared_ptr
std::weak_ptr<Test> wp2; // an empty weak_ptr - points to nothing
wp2 = sp; // wp2 now points to the new Test
std::weak_ptr<Test> wp3(wp2); // construct wp3 from a weak_ptr
wp1.reset(); // set to empty state in which it's pointing to nothing
std::cout << "finishing main...n";
}
2.2.2 使用weak_ptr
weak_ptr不能直接访问对象,只能先用lock()函数获取到shared_ptr,lock()函数会检验对象是否还存在。如果不存在,会得到空的shared_ptr。
weak_ptr默认是空的,不指像任何对象。它可以接受shared_ptr或者另一个weak_ptr的赋值。
代码语言:javascript复制#include <memory>
#include <iostream>
struct Test {
Test() { std::cout << "Test::Testn"; }
~Test() { std::cout << "Test::~Test destructorn"; }
void doSomeThing() { std::cout << "Test::doSomeThingn";}
int val_ { 0 };
};
int main() {
std::shared_ptr<Test> sp(new Test());
std::weak_ptr<Test> wp2(sp); // construct wp2 from a shared_ptr
std::shared_ptr<Test> sp2 = wp2.lock(); // get shared_ptr from weak_ptr
if(sp2) sp2->doSomeThing();
else std::cout << "The Test is gone!";
std::cout << "finishing main...n";
}
使用lock()得到的shared_ptr是一个全新的,sp2并不是sp。取到sp2务必检查shared_ptr管理的object还有效。
2.3 unique_ptr
unique_ptr像是shared_ptr的对立面,因为unique_ptr只运行自身的计数是1或者0,但是因为无需维护引用计数增减,所以他的内部可以设计地更简单,开销更小。
unique意味着拷贝构造和拷贝赋值函数是不被运行调用(内部通过=delete实现)。所以unique_ptr也不允许被值传递传参
代码语言:javascript复制#include <memory>
#include <iostream>
struct Test {
Test() { std::cout << "Test::Testn"; }
~Test() { std::cout << "Test::~Test destructorn"; }
int val_ { 0 };
};
int main() {
std::unique_ptr<Test> ptr(new Test); // Right
//std::unique_ptr<Test[]> p2(p1); // Wrong
std::unique_ptr<Test> p3;
//p3=p1; // Wrong
std::cout << "finishing main...n";
}
一些有用的使用方式,可以使用if(p)判断一个unique_ptr是否拥有某个对象,使用reset()成员函数或者=nullptr进行删除。
unique_ptr意味着如果需要进行对象的转移,可以使用move语义进行。比如说可以这样使用
代码语言:javascript复制#include <memory>
#include <iostream>
struct Test {
Test() { std::cout << "Test::Testn"; }
~Test() { std::cout << "Test::~Test destructorn"; }
int val_ { 0 };
};
//这里用到了return value是rvalue类型,所以这里可以利用return特性进行unique_ptr的move和reassign
std::unique_ptr<Test> createTest()
{
std::unique_ptr<Test> localPtr(new Test);
return localPtr; // localPtr会交出owership
}
int main() {
std::unique_ptr<Test> p1(createTest()); // 调用了移动构造函数,并读取了createTest的返回rvalue值
std::unique_ptr<Test> p2;
p2 = createTest(); // 调用了移动赋值函数,并读取了createTest的返回rvalue值
std::unique_ptr<Test> p3;
p3 = std::move(p1); // 使用了std::move函数
std::cout << "finishing main...n";
}