智能指针是代理模式的具体应用,它使用 RAII 技术代理了裸指针,能够自动释放内存, 无需程序员干预,所以被称为“智能指针”。
智能指针不是指针,而是一个对象,所以不要对其调用delete,它会自动管理初始化时的指针,在离开作用域时析构释放内存。
智能指针也没有定义加减运算,不能随意移动指针地址,这样避免了指针越界操作。
在使用上:
如果指针是“独占”使用,就应该选择 unique_ptr,它为裸指针添加了很多限制,更加安全 。
如果指针是“共享”使用,就应该选择 shared_ptr,它的功能非常完善,用法几乎与原始指针一样
使用智能指针要加头文件#include <memory>
工厂函数make_unique()、make_shared() 不只是返回智能指针对象,其内部也有优化。
如果你已经理解了智能指针,就尽量不要再使用裸指针、new 和 delete 来操作内存了。
unique_ptr
unique_ptr需要手动初始化,声明的时候必须用模板参数指定类型:
unique_ptr<int> ptr1(new int(10));
assert(*ptr1 = 10);
assert(ptr1 != nullptr);
unique_ptr<string> ptr2(new string("hello"));
assert(*ptr2 == "hello");
assert(ptr2->size() == 5);
也可以调用工厂函数,强制创建智能指针的时候必须初始化:
auto ptr3 = make_unique<int>(42);
assert(ptr3 && *ptr3 == 42);
auto ptr4 = make_unique<string>("god of war");
assert(!ptr4->empty());
unique_ptr表示该智能指针的所有权是唯一的,不允许共享,任何时候只能有一个人持有。
它禁止拷贝赋值,但是可以使用std::move() 显式地声明所有权转移:
auto ptr1 = make_unique<int>(42);
assert(ptr1 && *ptr1 == 42);
auto ptr2 = ptr1;
auto ptr2 = std::move(ptr1);
assert(ptr2 && *ptr2 == 42);
assert(ptr1);
指针的所有权就被转走了,原来的 unique_ptr 变成了空指针,新的 unique_ptr 接替了管理权,保证所有权的唯一性
shared_ptr
基本使用方式与unique_ptr并无不同:
shared_ptr<int> ptr1(new int(10));
assert(*ptr1 = 10);
shared_ptr<string> ptr2(new string("hello"));
assert(*ptr2 == "hello");
auto ptr3 = make_shared<int>(42);
assert(ptr3 && *ptr3 == 42);
auto ptr4 = make_shared<string>("zelda");
assert(!ptr4->empty());
不过它的所有权可以被安全共享,支持拷贝赋值
auto ptr1 = make_shared<int>(42);
assert(ptr1 && ptr1.unique() );
auto ptr2 = ptr1;
assert(ptr1 && ptr2);
assert(ptr1 == ptr2);
assert(!ptr1.unique() && ptr1.use_count() == 2);
assert(!ptr2.unique() && ptr2.use_count() == 2);
其内部使用引用计数,所以具有完整的”值语义“,可以在任何场合下代替原始指针。
不过维护引用计数的存储和管理都是成本,过度使用会降低运行效率。其引用计数也会带来循环引用,下面是简化后的典型例子:
#include <iostream>
#include <memory>
#include <assert.h>
using namespace std;
class Node final {
public:
using this_type = Node;
using shared_type = std::shared_ptr<this_type>;
shared_type next;
};
int main() {
auto n1 = make_shared<Node>();
auto n2 = make_shared<Node>();
assert(n1.use_count() == 1);
assert(n2.use_count() == 1);
n1->next = n2;
n2->next = n1;
assert(n1.use_count() == 2);
assert(n2.use_count() == 2);
}
这个例子很简单,你一下子就能看出存在循环引用。但在实际开发中,指针的关系可不像例 子那么清晰,很有可能会不知不觉形成一个链条很长的循环引用,复杂到你根本无法识别, 想要找出来基本上是不可能的。 想要从根本上杜绝循环引用,光靠 shared_ptr 是不行了,必须要用到weak_ptr 。
weak_ptr
它专门为打破循环引用而设计,只观察指针,不会增 加引用计数(弱引用),但在需要的时候,可以调用成员函数 lock(),获取 shared_ptr(强引用) 。用法如下
#include <iostream>
#include <memory>
#include <assert.h>
using namespace std;
class Node final {
public:
using this_type = Node;
using shared_type = std::weak_ptr<this_type>;
shared_type next;
};
int main() {
auto n1 = make_shared<Node>();
auto n2 = make_shared<Node>();
assert(n1.use_count() == 1);
assert(n2.use_count() == 1);
n1->next = n2;
n2->next = n1;
assert(n1.use_count() == 1);
assert(n2.use_count() == 1);
if (!n1->next.expired()) {
auto ptr = n1->next.lock();
assert(ptr == n2);
}
}
|