智能指针
现代 C++ 提供了智能指针用于防止程序存在内存和资源泄露,智能指针是 exception safe 的。
原始指针存在的问题
- 需要手动管理内存;
- 容易发生内存泄漏(忘记释放、出现异常等);
- 释放之后产生野指针。
cpp
int *p = new int();
// 记得要手动释放
delete p;
// 记得设为空指针
p = nullptr;智能指针就是为了解决原始指针存在的问题。
shared_ptr
采用引用计数的智能指针。
使用场景:将一个原始指针分配给多个所有者(例如,从容器返回了指针副本又想保留原始指针时)。
只有所有 shared_ptr 所有者超出了范围或放弃所有权,才会删除原始指针。 大小为两个指针;一个用于保存原始指针,另一个用于保存引用计数的对象。
示例:
cpp
std::shared_ptr<Person> ptr(new Person(10));
ptr->ShowAge();
(*ptr).ShowAge();
// 数组用法
std::shared_ptr<Person[]> ptr2(new Person[10]); // 通常使用这个
std::shared_ptr<Person> ptr3(new Person[10], [](Person* p) { delete[] p; });
// 分配给多个所有者
std::shared_ptr<Person> ptr4 = ptr;
std::shared_ptr<Person> ptr5(ptr);
// 引用计数
cout << ptr.use_count() << '\n'; // 3DANGER
shared_ptr 的使用有一些隐患需要注意。
以下代码会造成重复释放。
cpp
Person* p = new Person();
{
std::shared_ptr<Person> p1(p);
}
{
std::shared_ptr<Person> p2(p);
}以下代码会造成循环引用。
cpp
/**
* Copyright 2023 <Hardworking Bee>
*/
#include <iostream>
#include <memory>
using std::cout;
using std::shared_ptr;
class Person;
class Car {
public:
shared_ptr<Person> person_;
~Car() { cout << "~Car()" << '\n'; }
};
class Person {
public:
shared_ptr<Car> car_;
~Person() { cout << "~Person()" << '\n'; }
};
int main() {
{
// shared_ptr 循环引用。
// 两个 new 出来的对象都不会被释放。
shared_ptr<Person> person(new Person());
shared_ptr<Car> car(new Car());
person->car_ = car;
car->person_ = person;
}
return 0;
}weak_ptr
与 shared_ptr 配合使用。weak_ptr 提供对一个或多个 shared_ptr 实例拥有的对象的访问,但不参与引用计数。
使用场景:解决 shared_ptr 的循环引用问题。
让我们以上面 shared_ptr 循环引用的代码作为例子:
cpp
/**
* Copyright 2023 <Hardworking Bee>
*/
#include <iostream>
#include <memory>
using std::cout;
using std::shared_ptr;
using std::weak_ptr;
class Person;
class Car {
public:
// 这里使用 weak_ptr。
weak_ptr<Person> person_;
~Car() { cout << "~Car()" << '\n'; }
};
class Person {
public:
// 或者这里使用 weak_ptr,又或者两者都使用,都能解决循环引用的问题。
shared_ptr<Car> car_;
~Person() { cout << "~Person()" << '\n'; }
};
int main() {
{
// shared_ptr 循环引用。
// 两个 new 出来的对象都不会被释放。
shared_ptr<Person> person(new Person());
shared_ptr<Car> car(new Car());
person->car_ = car;
car->person_ = person;
}
return 0;
}unique_ptr
默认使用的智能指针。大小为一个指针。只允许原始指针的一个所有者。
以上三种智能指针头文件都是 <memory>。
原理
- 栈空间的对象在销毁时会自动调用析构函数;
- 重载运算符。
通过上面两点,我们可以自己实现一个智能指针。
cpp
/**
* Copyright 2023 <Hardworking Bee>
*/
#include <iostream>
using std::cout;
template <typename T>
class SmartPointer {
private:
T* ptr_;
public:
explicit SmartPointer(T* ptr) : ptr_(ptr) {}
~SmartPointer() {
delete ptr_;
// 由于后续没有任何地方使用 ptr_,所以这里不需要将 ptr_ 置为 nullptr。
}
T* operator->() const { return ptr_; }
T& operator*() const { return *ptr_; }
};
class Person {
private:
int age_;
public:
explicit Person(int age) : age_(age) { cout << "Person()" << '\n'; }
~Person() { cout << "~Person()" << '\n'; }
void ShowAge() const { cout << "Age: " << age_ << '\n'; }
};
int main() {
SmartPointer<Person> ptr(new Person(10));
ptr->ShowAge();
(*ptr).ShowAge();
return 0;
}DANGER
以上的实现有一个缺陷:不能指向数组。因为释放指针的地方没有处理指向数组的指针。