智能指针(独占、共享、弱引用)

一. unique_ptr独占的智能指针

std::unique_ptr 是 C++11 引入的标准库智能指针,用于管理动态分配的对象,它具有以下特点:

  1. 独占所有权unique_ptr 一次只能有一个指针拥有对分配对象的所有权。当一个 unique_ptr 拥有对象的所有权时,其他 unique_ptr 不能拷贝或分配相同的对象。这有助于防止悬垂指针问题。

  2. 自动释放资源:当 unique_ptr 超出其作用域(例如,函数结束)或者通过 std::move 转移所有权时,它会自动释放其所拥有的对象,确保资源不会泄漏。

  3. 无拷贝构造函数和拷贝赋值运算符unique_ptr 没有拷贝构造函数和拷贝赋值运算符,因为这会违反独占所有权的原则。但它有移动构造函数和移动赋值运算符,允许将所有权从一个 unique_ptr 移动到另一个。

  4. 支持自定义删除器:你可以使用自定义删除器函数或 lambda 表达式来指定在释放资源时应该执行的特殊操作,例如释放动态分配的数组或执行其他清理任务。

  5. RAII(资源获取即初始化)unique_ptr 的使用符合 RAII 原则,这意味着资源的获取和释放与对象的生命周期绑定在一起,从而确保资源的安全管理。

准备代码

#include<iostream>
#include <string>
#include<memory>
using namespace std;
class cat {
private:
	std::string name;
public:
	cat(string _name):name(_name) {
		cout << "构造函数 cat! " << endl;
	}
	~cat() {
		cout << "析构函数 cat!" << endl;
	}
	void cat_into() const {
		std::cout << name << std::endl;
	}
	void fixname(string _name) {
		name = _name;
	}
};

1.原始指针的创建

        (1)构造函数创建
int main() {
	cat c1("ok");
	c1.cat_into();
	{
		cat c1("haha");
		c1.cat_into();
	}
	cout << " ---------" << endl;
	return 0;
}

 

  •  使用构造函数构建,会自动使用析构函数进行删除,局部作用域内的原始指针也会在程序结束之前进行自动析构。

        (2)new 创建
int main() {
	cat* c1 = new cat("ok");
	c1->cat_into();
	{
		cat* c1 = new cat("yes");
		c1->cat_into();
	}
	cout << " ---------" << endl;
	return 0;
}

  • 不会自动析构删除

int main() {
	cat* c1 = new cat("ok");
	c1->cat_into();
	{
		cat* c1 = new cat("yes");
		c1->cat_into();
		delete c1;
	}
	delete c1;
	cout << " ---------" << endl;
	return 0;
}

  • 手动释放,局部原始指针在作用域内直接释放

int main() {
	cat* c1 = new cat("ok");
	int* a = new int(100);
	c1->cat_into();
	{
		cat* c1 = new cat("yes");
		a = new int(120);
		c1->cat_into();
		delete c1;
		delete a;
	}
	delete c1;
	delete a;
	cout << " ---------" << endl;
	return 0;
}

 

  • 原始指针在局部作用域内也进行了内存分配和释放,导致二次释放(非常不安全的)
  • 不同编译器可能有不同情况,以上为vs stdio 2022

2.三种创建 unique_ptr 方式

略微改动:
class cat {
private:
	std::string name;
public:
	cat(string _name):name(_name) {
		cout << "构造函数 ! "<<name << endl;
	}
	~cat() {
		cout << "析构函数 !" <<name<< endl;
	}
	void cat_into() const {
		std::cout << name << std::endl;
	}
	void fixname(string _name) {
		name = _name;
	}
};

(1)原始指针创建(不删除原始指针,原始指针删除会报错)
int main() {
	cat* c1 = new cat("ok");
	unique_ptr<cat> cat1{ c1 };
	
	c1->cat_into();
	c1->fixname("hello");
	cat1->cat_into();
	return 0;
}

 

  • 独占指针指向数据被修改,不符合独占特性!

(2)还是使用new进行创建
int main() {
	unique_ptr<cat> cat1{ new cat("ok") };
	cat1->cat_into();
	cat1->fixname("hello");
	cat1->cat_into();
	return 0;
}

  • 程序结束后自动析构释放

(3)使用make_unique
int main() {
	unique_ptr<cat> cat1 = make_unique<cat>("ok");
	cat1->cat_into();
	cat1->fixname("hello");
	cat1->cat_into();
	cout << "--------------" << endl;
	return 0;
}

  • 程序生命周期结束自动释放

3.get获取地址和解引用

int main() {

	unique_ptr<int> cat0{ new int(100) };
	unique_ptr<int> cat1 = make_unique<int>(200);
	
	cout << *cat0 << endl;
	cout << *cat1 << endl;
	cout << cat0.get() << endl;
	cout << cat1.get() << endl;
	cout << "--------------" << endl;
	return 0;
}

  • 没毛病(不用cat是因为cat需要写重载函数,懒得写了) 
// 重载解引用运算符 *,使其返回名字
    std::string operator*() const {
        return name;
    }

    // 自定义获取名字的成员函数
    std::string get_name() const {
        return name;
    }

4.函数调用与unique_ptr使用注意事项

(1)passing by value  (传值)
  • 需要使用 std::move 来转移内存拥有权
  • 如果参数直接传入 std::make_unique 语句,自动转换为move

  • 无法直接进行值传递 

  •  通过move转移内存所有权(注意在传入之后,unique_ptr的生命周期就跟随传入函数的生命周期,传入函数执行完后,unique_ptr也自动析构释放)

  •  实际上,使用move在进行值传递之后,原来的unique_ptr 就无法使用了

(2)passing by reference  (传引用)
  • 如果设置参数为const则不能改变指向
    • 比方说reset()
  • reset() 方法为智能指针清空方法
第一种:不加const

  • 传入的unique_ptr在函数执行完后被回收

第二种:加const

  • 传入的unique_ptr无法被改变指向,所以无法被销毁,在主程序结束后自动回收
  • 指向的对象属性是可以被修改的

(3)return by value  (返回值)
  • 指向一个local object
  • 可以用作链式函数

        一般不需要采用

二. shared_ptr 计数指针

        它是一种智能指针,用于管理动态分配的对象,特别是用于自动内存管理和避免内存泄漏。shared_ptr是C++标准库的一部分,定义在<memory>头文件中。它是C++为帮助自动内存管理而提供的智能指针之一,包括在C++11标准及更高版本中。

以下是shared_ptr的一些关键特点和特性:

  1. 共享所有权shared_ptr允许多个智能指针共享对同一动态分配对象的所有权。这意味着多个shared_ptr可以指向同一对象,而不会在对象不再需要时立即销毁它。

  2. 引用计数shared_ptr内部维护一个引用计数,用于跟踪有多少个shared_ptr指向相同的对象。每次创建或复制shared_ptr时,引用计数都会增加;当shared_ptr超出作用域或被显式重置时,引用计数减少。当引用计数为零时,对象将被自动销毁。

  3. 自动内存管理shared_ptr使得内存管理更容易,因为它会自动处理对象的销毁,无需手动释放内存。这有助于防止内存泄漏和减轻程序员的负担。

  4. 拷贝构造和赋值shared_ptr可以通过拷贝构造函数或赋值操作符进行复制,这将增加对象的引用计数。当shared_ptr超出作用域或被显式重置时,引用计数减少。当引用计数为零时,对象将被销毁。

  5. 弱引用:为了避免循环引用(当两个或多个shared_ptr相互引用时),C++还提供了weak_ptr,它是shared_ptr的弱引用版本。weak_ptr不增加引用计数,可以用来检查对象是否仍然存在,但不能直接访问对象。

使用shared_ptr可以提高C++程序的内存管理和安全性,减少资源泄漏的风险。然而,需要小心使用,以避免循环引用和悬空指针等问题。

准备代码

#include<iostream>
#include <string>
#include<memory>
using namespace std;
class cat {
private:
	std::string name;
public:
	cat(string _name):name(_name) {
		cout << "构造函数 ! "<<name << endl;
	}
	~cat() {
		cout << "析构函数 !" <<name<< endl;
	}
	void cat_into() const {
		std::cout << name << std::endl;
	}
	void fixname(string _name) {
		name = _name;
	}
};

1.计数特性

        常量

  • 在进行copy之后,不管清空哪个,计数 - 1 之后其余都不受影响。

        

        自定义对象

  • 同样,不管reset()置空哪个,其余都不受影响

  • 当计数为0时或者主程序结束时,指向内存自动析构回收

2.shared_ptr 与函数

(1) passed by value   值传递

 

  • 作为值传递进入函数,在函数内部计数 +1 ,函数结束之后该传入指针自动销毁
  • 传入指针可以修改其指向对象 

(2) passed by ref   引用

  • 引用时计数并未 +1 
  • 直接reset() 释放内存会导致错误
  • 使用 reset()改变指针指向,原来指向的内存被自动释放

  • const 防止指针的指向被修改 
  • 指向的对象属性依然可以修改

(3) returning by value
  • 链式

三. shared_ptr 和 unique_ptr

1. 不能将shared_ptr 转换为 unique_ptr

2. unique_ptr 可以转换为 shared_ptr(在设计时返回 unique_ptr 可以极大提高复用率)

  • 可以看到可以使用返回值将unique_ptr 返回并使用 shared_ptr 进行接收

四. weak_ptr弱引用的智能指针

std::weak_ptr 是C++中的一种智能指针,用于解决 std::shared_ptr 可能引发的循环引用问题

与 std::shared_ptr 不同,std::weak_ptr 并不共享对对象的所有权。它只是提供了对由 std::shared_ptr 管理的对象的非拥有(弱)引用。当所有 std::shared_ptr 对象都释放了其对管理对象的所有权后,std::weak_ptr 会自动变为无效状态。

std::weak_ptr 主要用于辅助进行对象生命周期的跟踪或访问。通过调用 std::weak_ptr 的 lock() 成员函数可以获取一个有效的 std::shared_ptr 对象,如果原始的 std::shared_ptr 对象已经被销毁,则返回一个空的 std::shared_ptr。

使用 std::weak_ptr 可以避免循环引用问题,因为它的存在不会增加对象的引用计数。这样,在存在循环引用的情况下,即使所有 std::shared_ptr 对象都释放了对对象的所有权,循环引用中的对象也可以正常地被销毁,从而避免内存泄漏。

  • expired()函数用于检查std::weak_ptr所指向的对象是否已经销毁或过期。如果std::weak_ptr所管理的对象已经被销毁,则expired()函数返回true,否则返回false

  • lock()函数用于获取一个有效的std::shared_ptr对象,从而可以安全地访问所指向的对象。当std::weak_ptr所管理的对象还存在时,通过lock()函数可以获得共享所有权的智能指针;如果对象已经销毁或过期,则lock()函数返回一个空的std::shared_ptr

通过输出可以看到,obj1 和 obj2 的 otherObj 弱引用在初始化时都是有效的(未过期)。接着,我们使用 weak_ptr 的 lock() 成员函数将其转换为有效的 shared_ptr 对象,然后判断获取的 shared_ptr 是否有效。

        总而言之,std::weak_ptr 提供了一种安全、灵活的方式来处理对象间的弱引用,帮助解决循环引用和内存管理的问题。

  • weak_ptr 并不拥有对象所有权
  • weak_ptr 并不能 解引用 和 调用 -> 

  • weak_ptr 可以调用 use_count(),但是并不计数

  • weak_ptr  的lock函数可以将自身转换为shared_ptr 指针,并且计数+1

代码增加:

	shared_ptr<cat> s;
    void set_ptr(shared_ptr<cat> _s) {
		s = _s;
	}

  • 在互相为对方的属性时,无法正常回收内存

改为弱引用:

    weak_ptr<cat> s;
    void set_ptr(shared_ptr<cat> _s) {
		s = _s;
	}

  •  弱引用之后可以正常回收

五.总结

普通指针的缺点:

  • 使用new创建对象时,必须使用 delete 删除;
  • 假如创建了一个对象指针,然后在局部作用域内把指针指向另一块内存,然后在局部作用域内对指针进行释放;然后出了局部作用域之后,再对指针进行释放就会造成二次释放。

智能指针:

  • 共同特性:超出作用域时,自动调用析构函数释放内存。
  • reset 用于改变指针指向,有参数则指向参数,无参数则直接析构释放内存。
  • 解决问题:
    • 空指针、野指针问题
    • 资源重复释放问题
    • 内存泄漏问题
    • 解决了new和delete不匹配的问题

适用场景:

        unique_ptr 独占指针
  • 适用于明确指定资源唯一拥有者的情况,防止多个指针同时管理同一块内存。
  • 例如:在动态分配内存时,使用 unique_ptr 可以确保只有一个指针拥有该内存块。

        shared_ptr 计数指针
  • 适用于需要多个指针共同拥有资源的情况。
  • 例如:在需要多个部分或函数间传递资源所有权的情况系啊,使用 shared_ptr 可以方便地实现共享。

        weak_ptr 弱引用指针
  • 适用于需要共享资源但是不拥有资源的情况。
  • 主要用来解决 shared_ptr 的循环引用问题。
  • 注意:weak_ptr 可以通过 lock 函数获取一个有效的 shared_ptr 来访问资源,当所有的 shared_ptr 都释放后,weak_ptr 将自动变为空指针。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值