一. unique_ptr独占的智能指针
std::unique_ptr
是 C++11 引入的标准库智能指针,用于管理动态分配的对象,它具有以下特点:
-
独占所有权:
unique_ptr
一次只能有一个指针拥有对分配对象的所有权。当一个unique_ptr
拥有对象的所有权时,其他unique_ptr
不能拷贝或分配相同的对象。这有助于防止悬垂指针问题。 -
自动释放资源:当
unique_ptr
超出其作用域(例如,函数结束)或者通过std::move
转移所有权时,它会自动释放其所拥有的对象,确保资源不会泄漏。 -
无拷贝构造函数和拷贝赋值运算符:
unique_ptr
没有拷贝构造函数和拷贝赋值运算符,因为这会违反独占所有权的原则。但它有移动构造函数和移动赋值运算符,允许将所有权从一个unique_ptr
移动到另一个。 -
支持自定义删除器:你可以使用自定义删除器函数或 lambda 表达式来指定在释放资源时应该执行的特殊操作,例如释放动态分配的数组或执行其他清理任务。
-
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
的一些关键特点和特性:
-
共享所有权:
shared_ptr
允许多个智能指针共享对同一动态分配对象的所有权。这意味着多个shared_ptr
可以指向同一对象,而不会在对象不再需要时立即销毁它。 -
引用计数:
shared_ptr
内部维护一个引用计数,用于跟踪有多少个shared_ptr
指向相同的对象。每次创建或复制shared_ptr
时,引用计数都会增加;当shared_ptr
超出作用域或被显式重置时,引用计数减少。当引用计数为零时,对象将被自动销毁。 -
自动内存管理:
shared_ptr
使得内存管理更容易,因为它会自动处理对象的销毁,无需手动释放内存。这有助于防止内存泄漏和减轻程序员的负担。 -
拷贝构造和赋值:
shared_ptr
可以通过拷贝构造函数或赋值操作符进行复制,这将增加对象的引用计数。当shared_ptr
超出作用域或被显式重置时,引用计数减少。当引用计数为零时,对象将被销毁。 -
弱引用:为了避免循环引用(当两个或多个
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 将自动变为空指针。