文章目录
一、原始指针容易发生内存泄漏
C++ 程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。但使用普通指针,容易造成内存泄露(忘记释放)、二次释放、程序发生异常时内存泄露等问题等。所以 C++11 就引入了智能指针。
C 语言中最常使用的是malloc()函数分配内存,free()函数释放内存,而 C++ 中对应的是new、delete关键字。malloc()只是分配了内存,而new则更进一步,不仅分配了内存,还调用了构造函数进行初始化。
二、使用构造函数和析构函数解决内存泄漏
C++ 中的构造函数和析构函数十分强大,可以使用构造和析构解决上面的内存泄漏问题。就算发生了异常,也能够保证析构函数成功执行。但是在多个引用和多线程的时候还是会出现问题。
C++11 中引入了智能指针(Smart Pointer),它利用了一种叫做 RAII(资源获取即初始化)的技术将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。这使得智能指针实质是一个对象,行为表现的却像一个指针。
智能指针主要分为shared_ptr、unique_ptr和weak_ptr三种,使用时需要引用头文件。C++98 中还有auto_ptr,基本被淘汰了,不推荐使用。而 C++11 中shared_ptr和weak_ptr都是参考boost库实现的。
三、shared_ptr共享的智能指针
// 初始化
shared_ptr<int> p1 = make_shared<int>(42);
shared_ptr<int> p2(new int(1024));
shared_ptr<int> p3 = p2;
// 获取值
cout << p1.get() << endl;
cout << *p1.get() << endl;
cout << *p1 << endl;
// 引用计数
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
cout << p3.use_count() << endl;
// 引用计数是否唯一
cout << p1.unique() << endl;
// 重置
p1.reset(new int(322));
// swap
p1.swap(p2);
四、weak_ptr弱引用的智能指针
shared_ptr的一个最大的陷阱是循环引用,循环引用会导致堆内存无法正确释放,导致内存泄漏。
要想解决上面循环引用的问题,只能引入新的智能指针std::weak_ptr。std::weak_ptr有什么特点呢?与std::shared_ptr最大的差别是在赋值的时候,不会引起智能指针计数增加。
shared_ptr<int> p = make_shared<int>(42);
weak_ptr<int> w(p); // 与p指向相同对象的weak_ptr, T必须能转换为sp指向的类型
w = p; // p可以是shared_ptr或者weak_ptr,赋值后w和p共享对象
w.reset(); // weak_ptr置为空
w.use_count(); // 与w共享对象的shared_ptr的计数
w.expired(); // w.use_count()为0则返回true,否则返回false
w.lock(); // w.expired()为true,返回空的shared_ptr;否则返回指向w的shared_ptr
五、unique_ptr独占的智能指针
unique_ptr相对于其他两个智能指针更加简单,它和shared_ptr使用差不多,但是功能更为单一,它是一个独占型的智能指针,不允许其他的智能指针共享其内部的指针,更像原生的指针(但更为安全,能够自己释放内存)。不允许赋值和拷贝操作,只能够移动。
六、性能与安全的权衡
使用智能指针虽然能够解决内存泄漏问题,但是也付出了一定的代价。以shared_ptr举例:
- shared_ptr的大小是原始指针的两倍,因为它的内部有一个原始指针指向资源,同时有个指针指向引用计数。
- 引用计数的内存必须动态分配。虽然一点可以使用make_shared()来避免,但也存在一些情况下不能够使用make_shared()。
- 增加和减小引用计数必须是原子操作,因为可能会有读写操作在不同的线程中同时发生。
七、智能指针的简单实现
#include<mutex>
template<typename T>
class Shared_ptr {
private:
// 成员变量
T *ptr_{nullptr};
int *ref_count_{nullptr};
std::mutex *mutex_{nullptr};
public:
/******************** 构造函数和析构函数 ********************/
// 默认构造函数
constexpr Shared_ptr() noexcept = default;
// 普通构造函数
explicit Shared_ptr(T *ptr) : ptr_{ptr} {
if (this->ptr_ != nullptr) {
this->ref_count_ = new int{1};
this->mutex_ = new std::mutex{};
}
}
// 从四行代码看右值引用:https://www.cnblogs.com/qicosmos/p/4283455.html
// - move 本意为 "移动",但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。
// - forward 完美转发,根据右值判断的推倒,调用forward 传出的值,若原来是一个右值,那么他转出来就是一个右值,否则为一个左值。这样的处理就完美的转发了原有参数的左右值属性,不会造成一些不必要的拷贝
// 拷贝构造函数
Shared_ptr(const Shared_ptr &rhs) noexcept: ptr_{rhs.ptr_}, ref_count_{rhs.ref_count_}, mutex_{rhs.mutex_} {
if (this->ptr_ != nullptr) {
this->addRefCount();
}
}
// 移动构造函数
Shared_ptr(Shared_ptr &&rhs) noexcept: ptr_{rhs.ptr_}, ref_count_{rhs.ref_count_}, mutex_{rhs.mutex_} {
// right hand side is nullptr
rhs.ptr_ = nullptr;
rhs.ref_count_ = nullptr;
rhs.mutex_ = nullptr;
}
// 析构函数
~Shared_ptr() noexcept {
this->decrRefCount();
}
/******************** 运算符重载 ********************/
// 拷贝赋值运算符
Shared_ptr &operator=(const Shared_ptr &rhs) {
Shared_ptr{rhs}.swap(*this);
return *this;
}
// 移动赋值运算符
Shared_ptr &operator=(Shared_ptr &&rhs) noexcept {
Shared_ptr{std::move(rhs)}.swap(*this);
return *this;
}
// 取值
T &operator*() const noexcept {
return *this->ptr_;
}
// 取指针
T &operator->() const noexcept {
return this->ptr_;
}
// 到 bool 的隐式转换
explicit operator bool() const noexcept {
return static_cast<bool>(ptr_);
}
/******************** 成员函数 ********************/
// 获取指针
T *get() const noexcept {
return ptr_;
}
// 获取引用计数
int use_count() const noexcept {
return this->ref_count_ == nullptr ? 0 : *this->ref_count_;
}
// 引用计数是否唯一
bool unique() const noexcept {
return *this->ref_count_ == 1;
}
// 重置指针
void reset() noexcept {
Shared_ptr{}.swap(*this);
}
// 重置指针
void reset(T *ptr) {
Shared_ptr{ptr}.swap(*this);
}
// swap函数
void swap(Shared_ptr &rhs) noexcept {
std::swap(this->ptr_, rhs.ptr_);
std::swap(this->ref_count_, rhs.ref_count_);
std::swap(this->mutex_, rhs.mutex_);
}
private:
// 增加引用计数
void addRefCount() {
mutex_->lock();
++(*ref_count_);
mutex_->unlock();
}
// 减少引用计数,如果为零,则释放指针
void decrRefCount() {
bool deleteflag = false;
mutex_->lock();
if (--(*ref_count_) == 0) {
delete ptr_;
delete ref_count_;
deleteflag = true;
}
mutex_->unlock();
if (deleteflag) {
delete mutex_;
}
}
};
// 函数模板
template<typename T>
auto make_Shared(T v) {
return Shared_ptr<T>{new T(v)};
}
八、附录:全部代码
/******************** 单元测试 ********************/
#include <iostream>
#include <memory>
using namespace std;
int main() {
// make_Shared
{
Shared_ptr<int> p1 = make_Shared<int>(42);
Shared_ptr<int> p2 = p1;
cout << *p1 << endl;
cout << p1.use_count() << endl;
cout << p1.unique() << endl;
}
// shared_ptr
{
// 初始化
shared_ptr<int> p1 = make_shared<int>(42);
shared_ptr<int> p2(new int(1024));
shared_ptr<int> p3 = p2;
// 获取值
cout << p1.get() << endl;
cout << *p1.get() << endl;
cout << *p1 << endl;
// 引用计数
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
cout << p3.use_count() << endl;
// 引用计数是否唯一
cout << p1.unique() << endl;
// 重置
p1.reset(new int(322));
// swap
p1.swap(p2);
}
// weak_ptr
{
shared_ptr<int> p = make_shared<int>(42);
weak_ptr<int> w(p); // 与p指向相同对象的weak_ptr, T必须能转换为sp指向的类型
w = p; // p可以是shared_ptr或者weak_ptr,赋值后w和p共享对象
w.reset(); // weak_ptr置为空
w.use_count(); // 与w共享对象的shared_ptr的计数
w.expired(); // w.use_count()为0则返回true,否则返回false
w.lock(); // w.expired()为true,返回空的shared_ptr;否则返回指向w的shared_ptr
}
}