文章首发
【重学C++】02 脱离指针陷阱:深入浅出 C++ 智能指针
前言
大家好,今天是【重学C++】系列的第二讲,我们来聊聊C++的智能指针。
为什么需要智能指针
在上一讲《01 C++如何进行内存资源管理》中,提到了对于堆上的内存资源,需要我们手动分配和释放。管理这些资源是个技术活,一不小心,就会导致内存泄漏。
我们再给两段代码,切身体验下原生指针管理内存的噩梦。
void foo(int n) {
int* ptr = new int(42);
...
if (n > 5) {
return;
}
...
delete ptr;
}
void other_fn(int* ptr) {
...
};
void bar() {
int* ptr = new int(42);
other_fn(ptr);
// ptr == ?
}
在foo
函数中,如果入参n
> 5, 则会导致指针ptr
的内存未被正确释放,从而导致内存泄漏。
在bar
函数中,我们将指针ptr
传递给了另外一个函数other_fn
,我们无法确定other_fn
有没有释放ptr
内存,如果被释放了,那ptr
将成为一个悬空指针,bar
在后续还继续访问它,会引发未定义行为,可能导致程序崩溃。
上面由于原生指针使用不当导致的内存泄漏、悬空指针问题都可以通过智能指针来轻松避免。
C++智能指针是一种用于管理动态分配内存的指针类。基于RAII设计理念,通过封装原生指针实现的。可以在资源(原生指针对应的对象)生命周期结束时自动释放内存。
C++标准库中,提供了两种最常见的智能指针类型,分别是std::unique_ptr
和 std::shared_ptr
。
接下来我们分别详细展开介绍。
吃独食的unique_ptr
std::unique_ptr
是 C++11 引入的智能指针,用于管理动态分配的内存。每个 std::unique_ptr
实例都拥有对其所包含对象的唯一所有权,并在其生命周期结束时自动释放对象。
创建unique_ptr
对象
我们可以std::unique_ptr
的构造函数或std::make_unique
函数(C++14支持)来创建一个unique_ptr
对象,在超出作用域时,会自动释放所管理的对象内存。示例代码如下:
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() {
std::cout << "MyClass constructed" << std::endl;
}
~MyClass() {
std::cout << "MyClass destroyed" << std::endl;
}
};
int main() {
std::unique_ptr<MyClass> ptr1(new MyClass);
// C++14开始支持std::make_unique
std::unique_ptr<int> ptr2 = std::make_unique<int>(10);
return 0;
}
代码输出:
MyClass constructed
MyClass destroyed
访问所管理的对象
我们可以像使用原生指针的方式一样,访问unique_ptr
所指向的对象。也可以通过get
函数获取到原生指针。
MyClass* naked_ptr = ptr1.get();
std::cout << *ptr2 << std::endl; // 输出 10
释放/重置所管理的对象
使用reset函数可以释放
unique_ptr所管理的对象,并将其指针重置为
nullptr或指定的新指针。
reset`大概实现原理如下
template<class T>
void unique_ptr<T>::reset(pointer ptr = pointer()) noexcept {
// 释放指针指向的对象
delete ptr_;
// 重置指针
ptr_ = ptr;
}
该函数主要完成两件事:
- 释放
std::unique_ptr
所管理的对象,以避免内存泄漏。 - 将
std::unique_ptr
重置为nullptr
或管理另一个对象。
code show time:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() {
std::cout << "MyClass constructed" << std::endl;
}
~MyClass() {
std::cout << "MyClass destroyed" << std::endl;
}
};
int main() {
// 创建一个 std::unique_ptr 对象,指向一个 MyClass 对象
std::unique_ptr<MyClass> ptr(new MyClass);
// 调用 reset,将 std::unique_ptr 重置为管理另一个 MyClass 对象
ptr.reset(new MyClass);
return;
}
移动所有权
一个对象资源只能同时被一个unique_ptr
管理。当尝试把一个unique_ptr
直接赋值给另外一个unique_ptr
会编译报错。
#include <memory>
int main() {
std::unique_ptr<int> p1 = std::make_unique<int>(42);
std::unique_ptr<int> p2 = p1; // 编译报错
return 0;
}
为了把一个 std::unique_ptr
对象的所有权移动到另一个对象中,我们必须配合std::move
移动函数。
#include <memory>
#include <iostream>
int main() {
std::unique_ptr<int> p1 = std::make_unique<int>(42);
std::unique_ptr<int> p2 = std::move(p1); // ok
std::cout << *p2 << std::endl; // 42
std::cout << (p1.get() == nullptr) << std::endl; // true
return 0;
}
这个例子中, 我们把p1
通过std::move
将其管理对象的所有权转移给了p2
, 此时p2
接管了对象,而p1
不再拥有管理对象的所有权,即无法再操作到该对象了。
乐于分享的shared_ptr
shared_ptr
是C++11提供的另外一种常见的智能指针,与unique_ptr
独占对象方式不同,shared_ptr
是一种共享式智能指针,允许多个shared_ptr
指针共同拥有同一个对象,采用引用计数的