关于RAII和智能指针

class srtuct其中的析构函数会将该值在函数结束后自动释放,免去了malloc free new delete等操作

拷贝函数默认是浅拷贝,但在结构体存在指针等变量的时候最好重新定义为深拷贝。避免被free多次

浅拷贝拷贝地址 深拷贝拷贝的是整个数组

浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

浅拷贝一般为:

int a = 8;
int *p;
p = &a;

char* str1 = "HelloWorld";
char* str2 = str1;

深拷贝则为:

int a = 8;
int *p = new int;
*p = a;

char* str1 = "HelloWorld";
char* str2 = new char[strlen(str1)];
memcpy(str2,str1,strlen(str1))

移动则不同,移动是浅拷贝,因为在移动后,原来的就被释放掉了

vector<int> v1;
vector<int> v2;
v1 = std::move(v2);

v2在移动至v1后就被释放掉了

所以C++11提出了智能指针unique_ptr

std::unique_ptr<C>p = std::make_unique<C>();

void 但是智能指针是不能被拷贝的

比如要是

void func(std::unique_ptr<C> p){
p->do_something();
}
int main(){
    std::unique_ptr<C> p = std::make_unique<C>();
    func(p);//这里p会出错,因为不可以被拷贝
}

这是由于unique自带的析构函数是delete 所以必须删除拷贝构造函数(浅拷贝)。

那我们可以通过以下两种方法调用智能指针:

void func(C* p){
    p->do_something();
}

int main(){
    std::unique_ptr<C> p = std::make_unique<C>();
    func(p.get());//获取初始指针(这是在你只是单纯的要调用这个指针,并不夺取控制权)
}
std::vector<std::unique_ptr<C>> objlist;

void func(std::unique_ptr<C> p) {
    objlist.push_back(std::move(p));  // 进一步移动到 objlist
}


int main(){
    std::unique_ptr<C> p = std::make_unique<C>();
    printf("移交前:%p\n", p.get());  // 不为 null
    func(std::move(p));//这里会将p的生命周期移交给func函数,然后p的生命周期就和objlist一样了
    printf("移交后:%p\n", p.get());  // null,因为移动会清除原对象
    return 0;
}

但是移动之后这个p就消失掉了

所以可以先用一个指针去拉着p的原始指针。就有点像两个人本来是拉着手的,现在要松开,但是松开之后就找不到你拉的那个手了,那就需要现在来一个你熟悉的人,让他和你一起拉着,之后你再松开,这样就还是可以找到你的原始指针的(仅限objlist没有在do_someting之前删除)

int main() {
    std::unique_ptr<C> p = std::make_unique<C>();

    C *raw_p = p.get();
    func(std::move(p));

    raw_p->do_something();  // 正常执行,raw_p 保留了转移前的指针

    return 0;
}

所以就要引出shared_ptr,这是一种更加智能的指针,它可以允许拷贝,他的规则是引入了计数机制,当他被拷贝一次,计数就会加1,被解构就减1,当引用计数归零才删除指针。所以可以实现安全的浅拷贝。但是这是牺牲效率换来的。

但是shared_ptr还有概率出现循环死锁这种问题,所以就要使用一个弱指针weak_ptr

#include <cstdio>
#include <memory>
#include <vector>

struct C {
    ......
};

std::vector<std::shared_ptr<C>> objlist;

void func(std::shared_ptr<C> p) {
    objlist.push_back(std::move(p));  // 这里用移动可以更高效,但不必须
}

int main() {
    std::shared_ptr<C> p = std::make_shared<C>(); // 引用计数初始化为1

    printf("use count = %ld\n", p.use_count());   // 1

    std::weak_ptr<C> weak_p = p;        // 创建一个不影响计数器的弱引用

    printf("use count = %ld\n", p.use_count());   // 1

    func(std::move(p));  // 控制权转移,p 变为 null,引用计数加不变

    if (weak_p.expired())
        printf("错误:弱引用已失效!");
    else
        weak_p.lock()->do_something();  // 正常执行,p 的生命周期仍被 objlist 延续着

    objlist.clear();    // 刚刚 p 移交给 func 的生命周期结束了!引用计数减1,变成0了

    if (weak_p.expired())              // 因为 shared_ptr 指向的对象已释放,弱引用会失效
        printf("错误:弱引用已失效!");
    else
        weak_p.lock()->do_something();  // 不会执行

    return 0;  // 到这里最后一个弱引用 weak_p 也被释放,他指向的“管理块”被释放
}

这里的lock()是可以产生一个强的shared_ptr,可以理解为奥特曼变身了,weak也可以当成强引用使用了。但是要注意,当 weak_ptr 执行 lock() 函数时,不会将引用计数增加。这是因为 weak_ptr 是一种弱引用(weak reference),它只能指向由 shared_ptr 管理的对象,但并不拥有该对象的所有权。

可以这么理解,当 weak_ptr 调用 lock() 函数后得到一个有效的 shared_ptr 对象时,可以通过修改该对象的成员变量或调用其成员函数来改变原来 shared_ptr 中管理的对象。

这是因为 shared_ptrweak_ptr 共享同一个引用计数,而 weak_ptr 只是表明在某个时间点内是否有至少一个有效的 shared_ptr 指向被管理的对象。当使用 lock() 函数获得一个 shared_ptr 之后,相当于增加了一份对应的引用计数,在新的 shared_ptr 对象生命周期内,可以随意修改被管理对象的状态和数据内容。如果所有 shared_ptr 对象都释放,则表示该对象引用计数为 0,最终会自动销毁。

可能觉得太复杂了,不用担心,在以下几种类型中,都是符合三五法则的

int id //基础类型
std::vector<int> vec //基础stl容器
shared_ptr<Object> p //智能指针
Object* p //原始指针 是unique_ptr里.get()出来的

//以下是不安全的

char*ptr //通过new,malloc分配的
GLint tex //基础类型int ,但是对应着某种资源(这个我也没懂,基本很少遇到)
vector<Object*> obj //stl容器,但是存放了不安全的对象

三五法则定义放在这里:

三/五/零之法则 - cppreference.com

还有就是为什么智能指针不可以被拷贝(拷贝受限)上面匆匆提了一嘴,下面补充:

智能指针的拷贝行为是受限的,主要原因如下:

  1. 所有权语义:智能指针基于所有权语义,即一个对象只能由一个智能指针来拥有和管理。拷贝一个智能指针会导致多个智能指针共享同一个资源的所有权,这可能会破坏智能指针的设计目的和语义。

  2. 所有权转移:智能指针通常支持所有权的转移,即通过移动语义(move semantics)来将资源的所有权从一个智能指针转移到另一个智能指针。这样可以确保资源的唯一所有权,避免资源的重复释放或访问问题。

由于拷贝行为与智能指针的设计目的和语义冲突,C++标准库中的std::unique_ptr禁止拷贝操作,只支持移动语义;而std::shared_ptr通过使用引用计数来管理资源,允许多个shared_ptr对象共享同一个资源的所有权。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值