1. std::shared_ptr
std::shared_ptr 是一种智能指针,它能够记录多少个 shared_ptr 共同指向一个对象。
delete,当引用计数变为零的时候就会将对象自动删除。
使用 std::shared_ptr 仍然需要使用 new 来调用,这使得代码出现了某种程度上的不对称。
std::make_shared 就能够用来消除显式的使用 new,所以 std::make_shared 会分配创建传入参数中的对象,并返回这个对象类型的 std::shared_ptr 指针。例如:
cpp
#include <iostream>
#include <memory>
void foo(std::shared_ptr<int> i)
{
(*i)++;
}
int main()
{
auto pointer = std::make_shared<int>(10);
foo(pointer);
std::cout << *pointer << std::endl; // 结果是11
return 0;
//出了作用范围后自动销毁
}
std::shared_ptr 可以通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数,
并通过 use_count() 来查看一个对象的引用计数。例如:
cpp
auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // pointer2 已 reset; 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // pointer3 已 reset; 0
当使用 shared_ptr 的时候每添加一个指向同一个对象的指针就会增加一个计数,这个计数是总计数,所以在打印的第一部分会发现调用三个指针分别查看这个对象的引用数都是 3 。
根据不同的指针使用 reset() 只会消除对应指针对这块内存的所有权,第二处打印前对 p2 进行了减少计数的操作,此时分别打印会发现 p1 和 p3 都是 2 ,而 p2 变成了 0。这是因为每个 shared_ptr 在添加的时候把一个数公共的加进了计数中,所以 reset() 操作把自己对这块内存的所有权放弃后,总计数就会减 1 。但是既然已经放弃了所有权,所以 p2 就无法正常显示刚才那块内存的计数,所以就显示 0。
第三段打印时也是相同的原因。
2. std::unique_ptr
std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:
cpp
std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法
既然是独占,那就是不可复制。但是,我们可以利用 std::move 将其转移给其他的 unique_ptr,例如:
cpp
#include <iostream>
#include <memory>
struct Foo
{
Foo() { std::cout << "Foo::Foo" << std::endl; }
~Foo() { std::cout << "Foo::~Foo" << std::endl; }
void foo() { std::cout << "Foo::foo" << std::endl; }
};
void f(const Foo &)
{
std::cout << "f(const Foo&)" << std::endl;
}
int main()
{
//创造一个类型为 unique_ptr<Foo> 的智能指针 p1 初始化通过Foo的构造函数
std::unique_ptr<Foo> p1(std::make_unique<Foo>());
// p1 不空, 输出
if (p1) p1->foo();
{
//通过 move 将 p1 转移给 p2 完成 p2 的初始化,此时 p1 失去了这块内存的访问权
std::unique_ptr<Foo> p2(std::move(p1));
// p2 不空, 输出
f(*p2);
// p2 不空, 输出
if(p2) p2->foo();
// p1 为空, 无输出
if(p1) p1->foo();
//再通过 move 把 p2 的所有权转给 p1
p1 = std::move(p2);
// p2 为空, 无输出
if(p2) p2->foo();
std::cout << "p2 被销毁" << std::endl;
}
// p1 不空, 输出
if (p1) p1->foo();
// Foo 的实例会在离开作用域时被销毁
}
3. std::weak_ptr
如果你仔细思考 std::shared_ptr 就会发现依然存在着资源无法释放的问题。看下面这个例子:
cpp
class Child;
class Parent
{
public:
std::shared_ptr<Child> child; // 爸爸持有儿子的 shared_ptr
~Parent() { std::cout << "爸爸走了\n"; }
};
class Child
{
public:
std::shared_ptr<Parent> parent; // 儿子持有爸爸的 shared_ptr
~Child() { std::cout << "儿子走了\n"; }
};
int main()
{
auto dad = std::make_shared<Parent>();
auto son = std::make_shared<Child>();
dad->child = son; // 爸爸指向儿子,计数:儿子对象 = 2 (son + dad->child)
son->parent = dad; // 儿子指向爸爸,计数:爸爸对象 = 2 (dad + son->parent)
// main 结束,dad 和 son 销毁,计数各自减为 1
// 内存泄漏,两个对象互相抱着,无法释放
}
爸爸要等儿子走才走,儿子要等爸爸走才走,结果谁也走不了,导致内存泄漏。
解决这个问题的办法就是使用弱引用指针 std::weak_ptr,std::weak_ptr 是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用)。
cpp
class Child
{
public:
std::weak_ptr<Parent> parent; // 改成弱引用!儿子知道有爸爸,但不"拽着"爸爸不让走
~Child() { std::cout << "儿子走了\n"; }
};
std::weak_ptr 没有 * 运算符和 -> 运算符,所以不能够对资源进行操作,它可以用于检查std::shared_ptr 是否存在。
使用 expired() 方法能在对象资源未被释放时返回 false,否则返回 true 。除此之外,还可以升级成强引用,通过 lock() 可以升级成临时的 shared_ptr。