C++【内存管理】| 【智能指针】动态内存管理

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、简介

是一种抽象类型,由类模板来实现,借由类别的析构函数来达到自动释放指针所指向的存储器或对象;

  • 支持自动、异常安全的对象生命周期管理;

二、四种智能指针

1、auto_ptr

该智能指针管理通过new创建auto_ptr获得的对象,并在其自身被销毁时删除该对象

注意

  • 不可用多个指向同一个对象,当使用拷贝构造赋值运算符时,右值会变为nullptr
  • 在C++11被弃用,C++17被删除;

2、unique_ptr

template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;
复制代码

通过指针拥有和管理对象,且对该对象具有唯一权,没有其他操作能删除该对象;

  • 该智能指针禁止拷贝赋值操作,但可通过std::move移动赋值;
  • 可通过reset进行更改;
  • 对于数组对象提供[]运算符;

关于为何对接管对象具有唯一权

由于当该对象被删除时,若有其他指针托管该对象,则其他的指针将会无效;

2.1 测试案例

#include <iostream>
#include <memory>
#include <string>

using namespace std;


class A{
public:
	A(int a) : m_a(a) {
		cout << "A..." << endl;
	}

	~A() {
		cout << "Bye..." << endl;
	}

	int getA() const {
		return m_a;
	}

private:
	int m_a;
};
复制代码

创建

void test_unique_ptr() {

	// 方式1 
	std::unique_ptr<A> u_a(new A(1));
	// 方式2
	std::unique_ptr<A> u_b;
	u_b.reset(new A(2));
	// 方式3
	std::unique_ptr<A> u_c = std::make_unique<A>(3);
	cout << "u_a: " << u_a->getA() << endl;
	cout << "u_b: " << u_b->getA() << endl;
	cout << "u_c: " << u_c->getA() << endl;

	// std::move
	std::unique_ptr<A> u_d = std::move(u_c);
	cout << "u_d : " << u_d ->getA() << endl;	
}
复制代码

在这里插入图片描述 reset

void test_unique_ptr() {

	std::unique_ptr<A> u_b(new A(2));
	u_b.reset(new A(4));	// 会先释放内存

	cout << "u_b: " << u_b->getA() << endl;
}
复制代码

在这里插入图片描述

3、weak_ptr

template <class T> class weak_ptr;
复制代码

弱管理对象的类型,提供了对其管理的资源的一个访问手段,为协助shared_ptr

  • 析构和构造不会造成计数的增加或减少;
  • 可以解决两个shared_ptr互相引用造成的死锁问题,导致资源不会释放;
  • 该智能指针没有重载*以及->,故不能直接操作对象,由于可能在操作的时候该对象被释放而引发错误;

3.1 提供的成员函数

operator=

  • 不会获得所有权,且不会增加计数;
  • shared_ptr可以直接分配给weak_ptr
  • 当一个weak_ptr分配给shared_ptr需要使用lock来完成;
void test_weak_ptr(){
	std::shared_ptr<A> ptr1(new A(1));
	std::shared_ptr<A> ptr2;
	std::weak_ptr<A> wptr;

	// 将shared_ptr转换为weak_ptr
	wptr = ptr1;

	// 将weak_ptr转换为shared_ptr
	ptr2 = wptr.lock();

	cout << "ptr1:" << &(*ptr1) << endl;
	cout << "ptr2:" << &(*ptr2) << endl;
}
复制代码

在这里插入图片描述

swap

交换对象;

void test_weak_ptr(){
	std::shared_ptr<A> ptr1(new A(1));
	std::shared_ptr<A> ptr2(new A(2));
	std::weak_ptr<A> wptr1 = ptr1;
	std::weak_ptr<A> wptr2 = ptr2;

	wptr1.swap(wptr2);
	cout << "ptr1: " << &(*ptr1) << endl;
	cout << "ptr2: " << &(*ptr2) << endl;
	cout << "wptr1: " << &(*wptr1.lock()) << endl;
	cout << "wptr2: " << &(*wptr2.lock()) << endl;
}
复制代码

在这里插入图片描述

reset

重置变为空;

void test_weak_ptr(){
	std::shared_ptr<A> ptr1(new A(1));
	std::weak_ptr<A> wptr1 = ptr1;
	wptr1.reset();

	std::cout << "wptr1 " << (wptr1.expired() ? "" : "没有") << " 过期\n";
}
复制代码

在这里插入图片描述

use_count

共享所有权的shared_ptr对象的数量

  • weak_ptr不计算在内;

expired

  • 检查是否过期, weak_ptr对象是否为空;

lock

若未过期,返回一个shared_ptr,包含由weak_ptr对象保留的信息;

3.2 应用场景

  • 辅助shared_ptr的使用;
  • 出现于该资源能使用则使用,不能使用则不用;

4、shared_ptr

template <class T> class shared_ptr;
复制代码

对象具有获得指针所有权并共享该所有权的能力,持有的资源能在多个shared_ptr之间共享;

  • 当多一次对该对象的引用,则引用计数加一,若发生析构,则减一;
  • 当引用计数为0时,则释放该资源;

4.1 常用成员函数

operator=

  • 复制:使该对象的引用计数加1;
  • 移动:则原对象将变为空;
void test_shared_ptr() {
	std::shared_ptr<A> ptr1(new A(1));
	std::shared_ptr<A> ptr2;

	// copy
	cout << "ptr1:" << ptr1.use_count() << endl;
	cout << "ptr2:" << ptr2.use_count() << endl;
	ptr2 = ptr1;

	cout << "ptr1:" << ptr1.use_count() << endl;
	cout << "ptr2:" << ptr2.use_count() << endl;

	// move
	ptr2 = std::make_shared<A>(2);
	cout << "ptr1:" << ptr1.use_count() << endl;
	cout << "ptr2:" << ptr2.use_count() << endl;
}
复制代码

在这里插入图片描述

swap

交换托管对象内容,不会破坏或更改引用计数;

void test_shared_ptr() {
    std::shared_ptr<A> ptr1(new A(1));
    std::shared_ptr<A> ptr2(new A(2));

    cout << "ptr1 count: " << ptr1.use_count() << " ptr1: " << ptr1 << endl;
    cout << "ptr2 count: " << ptr2.use_count() << " ptr2: " << ptr2 << endl;

    ptr1.swap(ptr2);
    cout << "ptr1 count: " << ptr1.use_count() << " ptr1: " << ptr1 << endl;
    cout << "ptr2 count: " << ptr2.use_count() << " ptr2: " << ptr2 << endl;
}
复制代码

在这里插入图片描述

reset

重置指针,若原先由托管,则会将原先的删除;

  • 若不传入参数,则原对象的引用计数会减一;
  • 若传入参数,则该对象的引用计数会加一;
void test_shared_ptr() {
	std::shared_ptr<A> ptr1(new A(1));

	ptr1.reset();
	cout << "ptr1引用计数: " << ptr1.use_count() << endl;
	ptr1.reset(new A(2));
	cout << "ptr1引用计数: " << ptr1.use_count() << endl;
}
复制代码

在这里插入图片描述

get

获取指针;

void test_shared_ptr() {
	A *a = new A(2);
	std::shared_ptr<A> ptr1(a);

	cout << "ptr1:" << ptr1.use_count() << endl;

	if (a == ptr1.get()) {
		cout << "equal..." << endl;
	}
}
复制代码

在这里插入图片描述

operator*

取消引用对象;

void test_shared_ptr() {
	A *a = new A(2);
	std::shared_ptr<A> ptr1(a);

	cout << "ptr1:" << ptr1.use_count() << endl;
	
	if (a == ptr1.get()) {
		cout << "equal..." << endl;
	}

	if (&(*ptr1) == a){
		cout << "equal..." << endl;
	}
}
复制代码

在这里插入图片描述

operator->

取消引用对象成员,即直接调用对象成员;

void test_shared_ptr() {
	A *a = new A(2);
	std::shared_ptr<A> ptr1(a);

	cout << a->getA() << endl;
}
复制代码

在这里插入图片描述

use_count

获取引用计数;

unique

检查是否唯一;

operator bool

检查是否不为空;

operators ==, !=, <, <=, >, >=

重载了运算符;

operator<<

make_shared

制作shared_ptr;

4.2 引用计数

当计数器为0时,智能指针将会自动释放

何时计数器会递增

  • 当使用拷贝时shared_ptr<T> p(q)p时q的拷贝,计数器会递增;
  • 当两个智能指针相互转换时,左边的指针对象会增加
  • 当它作为参数传递时;
  • 当它作为函数返回值时;

何时计数器会递减

  • 当两个智能指针相互转换时,右边的指针对象会递减
  • 当智能指针被销毁时;

使用new返回的指针来初始化智能指针;

  • 一般初始化智能指针的指针必须指向动态内存;
  • 由于之智能指针内部的构造函数是explicit,不能进行隐式转换,必须使用直接初始化
  • 由于不清楚对象何时销毁,则最好不使用内置指针来访问一个智能指针且不使用get()初始化或赋值;
shared_ptr<T> p(new T(1));
复制代码

4.3 share_ptr与new结合使用

当new与share_ptr作为参数时

// 函数声明
int func();
void test(std::shared_ptr<int> p, int func);
复制代码

由于智能指针内部的构造函数是explicit,故不能直接传入new创建的指针;

// 可考虑使用以下:
test(std::shared_ptr<int> (new int), func());
复制代码

第一个参数被分为俩部分:std::shared_ptr<int>new int; 由于编译器考虑高效的操作,故传入参数的执行顺序:

  • 执行new int
  • 调用func
  • 调用智能指针构造

但需要考虑到,万一func函数内部出现异常,则此时将会引起内存泄漏,由于new int返回的指针遗失;


故将实参一先单独出来:

std::shared_ptr<int> p(new int);
test(p, func());
复制代码

4.4 enable_shared_from_this

在子类中继承该类,能够让子类的对象创建指向自身的shared_ptr实例;

  • 获取返回会增加引用计数;
class A : public enable_shared_from_this<A>{
public:
    A(int a) : m_a(a) {
        cout << "A..." << endl;
    }

    ~A() {
        cout << "Bye..." << endl;
    }

    shared_ptr<A> GetThis() {
        return shared_from_this();
    }

    int getA() const {
        return m_a;
    }

private:
    int m_a;
};


void test_shared_ptr() {
    std::shared_ptr<A> ptr(new A(1));

    ptr->GetThis();
    cout << ptr.use_count() << endl;
}

复制代码

在这里插入图片描述

注意事项

  • 不能将栈对象,来获取shared_from_this,会导致程序奔溃;
void test_shared_ptr() {
	A a(1);
	auto p = a.GetThis();
}
复制代码

5、注意事项

  • 分清楚什么情况下使用哪一种指针:
    • 当该资源只用在当前且不共享的情况下,使用unique_ptr
    • 当智能指针不需要管理对象的生命周期下,使用weak_ptr
    • 若要其他地方共享使用,使用shared_ptr
  • 注意使用时,该资源是否有效
  • 作为类成员的时,一般优先考虑向前声明,而不是作为头文件导入;

猜你喜欢

转载自juejin.im/post/7094959625228779534