Skip to content

智能指针

现代 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';  // 3

DANGER

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

以上的实现有一个缺陷:不能指向数组。因为释放指针的地方没有处理指向数组的指针。

References