智能指针本质上是一个原始指针的包装,也是一个模板。调用new的时候,不需要调用delete,甚至不需要调用new
智能指针的区别在于管理底层指针的方式,shared_ptr允许多个指针指向同一个对象,unique_ptr独占所指的对象。还有一个weak_ptr的伴随类,是一种弱引用,指向shared_ptr所管理的对象
unique_ptr(作用域指针)
unique_ptr实际上并不是指针,而是一个对象。所以,不要企图对它调用 delete,它会自动管理初始化时的指针,在离开作用域时析构释放内存。也没有定义加减运算,不能随意移动指针地址。unique_ptr必须初始化,否则直接操作空指针会出错。为了避免这种错误,可以调用工厂函数 make_unique(),强制创建智能指针的时候必须初始化。
unique_ptr不能复制,也就是说两个unique_ptr不能指向相同的内存。因为如果两个指针指向同一块内存,但其中一个被释放了,那么另一个就指向了被释放的内存
#include <iostream>
class Entity
{
public:
Entity()
{
std::cout << "created Entity" << std::endl;
}
~Entity()
{
std::cout << "destroyed Entity" << std::endl;
}
void print() { std::cout << "print entity" << std::endl; }
};
int main()
{
{
//ScopedPtr e = new Entity(); //隐式转换
//std::unique_ptr<Entity> entity = new Entity(); //不能这样构造,因为unique_ptr使用了explicit,必须显式调用构造函数,没有隐式构造函数的转换
//std::unique_ptr<Entity> entity (new Entity());
std::unique_ptr<Entity> entity = std::make_unique<Entity>();//更推荐,主要原因是出于异常安全
entity->print();
}
return 0;
因为unique_ptr不能复制,也就不能进行函数传参
unique_ptr的所有权:指针的所有权是“唯一”的,不允许共享,为了实现这个目的,unique_ptr 应用了 C++ 的“转移”(move)语义,同时禁止了拷贝赋值,所以,在向另一个 unique_ptr 赋值的时候,要特别留意,必须用 std::move() 函数显式地声明所有权转移。赋值操作之后,指针的所有权就被转走了,原来的 unique_ptr 变成了空指针,新的 unique_ptr 接替了管理权,保证所有权的唯一性
auto ptr1 = make_unique<int>(42); // 工厂函数创建智能指针
assert(ptr1 && *ptr1 == 42); // 此时智能指针有效
auto ptr2 = std::move(ptr1); // 使用move()转移所有权
assert(!ptr1 && ptr2); // ptr1变成了空指针
声明数组:
std::unique_ptr<int[]> ptr{new int[5] {1, 2, 3, 4, 5}};
//或者std::unique_ptr<int[]> ptr{std::make_unique<int[]>(5)};
for (int i = 0; i < 5; i++) {
std::cout << ptr[i] << " ";
}
reset,get和release:
reset释放智能指针的内存,将内存还给操作系统,并将智能指针的地址重设为0
get返回原始指针
release将unique_ptr设为nullptr,但不会释放内存空间
std::unique_ptr<int[]> ptr{new int[5] {1, 2, 3, 4, 5}};
int* a = ptr.get();
std::cout << ptr << std::endl; //00CB0780
std::cout << a << std::endl; //00CB0780
ptr.reset();
std::cout << ptr << std::endl; //00000000
std::cout << a << std::endl; //00CB0780
shared_ptr(共享指针)
与 unique_ptr 的最大不同点:它的所有权是可以被安全共享的,也就是说支持拷贝赋值,允许被多个“人”同时持有,就像原始指针一样
shared_ptr的工作方式是引用计数(可以跟踪指针有多少个引用,如果发生拷贝赋值,也就是共享的时候,引用计数就增加,而发生析构销毁的时候,引用计数就减少,一旦引用计数达到0,就删除了),但引用计数的存储和管理都是成本(但成本较低)。
shared_ptr需要分配一块内存,叫控制块,用来存储引用计数。如果首先创建一个new Entity,然后将其传递给shared_ptr构造函数,它必须做两次分配,先做一次new Entity的分配,再做一次shared_ptr控制内存块的分配,使用make_shared能把它们组合起来,更有效。
int main()
{
{
std::shared_ptr<Entity> e0;
{
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
e0 = sharedEntity; //增加引用计数
}//此时对象还没被销毁,因为引用计数还没达到0
}
return 0;
}
注意:make_shared不支持数组。通过use_count()可以获得当前有多少个引用计数
shared_ptr 的引用计数也导致了一个新的问题,就是“循环引用”,这在把 shared_ptr 作为类成员的时候最容易出现,典型的例子就是链表节点。weak_ptr专门为打破循环引用而设计,只观察指针,不会增加引用计数(弱引用),但在需要的时候,可以调用成员函数 lock(),获取 shared_ptr(强引用)。
可以把shared_ptr赋给weak_ptr,把shared_ptr赋给shared_ptr,会增加引用计数;而赋给weak_ptr不会增加引用计数。
std::weak_ptr<Entity> weakEntity = sharedEntity;