智能指针

内存泄漏: 当我们动态地申请了一片内存以后,在程序中没有主动去释放它的时候,导致他在程序中再也访问不到了。所谓泄露的内存是指:进程中,属于进程,但是进程却访问不到的那片内存。

智能指针的自动析构功能能很好的解决内存泄漏问题。但是凡事都有例外,例如遇到环形引用的情况:

环形引用:

示例代码:

#include<iostream>
#include<memory>
using namespace std;
class A {
public:
    A() {
        cout << "default constructor" << endl;
    }
    ~A() {
        cout << "destructor" << endl;
    }
    shared_ptr<A> ptr;
};

int main() {
    shared_ptr<A> p = make_shared<A>(); //p指针指向了一个A1对象,A1对象里有一个指针指向了q指针
    shared_ptr<A> q = make_shared<A>(); //q指针指向了一个A2对象,A2对象里有一个指针指向了p指针
    p->ptr = q;
    q->ptr = p;
    cout << p.use_count() << endl;
    cout << q->ptr.use_count() << endl;
    p = nullptr;
    cout << q->ptr.use_count() << endl;
    cout << "end main" << endl;

    return 0;
}

输出结果:

default constructor
default constructor
2
2
1
end main

可以发现:因为A2里的ptr对象还指着A1,所以即便p指针不再指向A1对象了,也不会触发自动析构函数把A1对象给析构掉。

这显然不是我们的原意,因为一旦q对象也指向了空地址,那么A1和A2两个对象因为互指的原因依然不会被析构。但是进程现在已经无法再通过指针找到这两个对象的内存,于是乎将造成内存泄漏。

模拟实现一个shared_ptr:

  1. 指向A类对象;
  2. 引用计数的设计
#include<iostream>
using namespace std;
class A {
public:
    A(int x = 3, int y = 4) : x(x), y(y) {
        cout << "default constructor" << endl;
    }  
    int x, y;
    ~A() {
        cout << "destructor" << endl;
    }
};
ostream &operator<<(ostream &out, const A &a) {
    out << "class A : "<< a.x << "," << a.y;
    return out;
}
class shared_ptr {
public:
  // TODO
};

shared_ptr make_shared() {
  // TODO
}

int main() {
    shared_ptr p = make_shared(); //RVO返回值优化 
    p->x = 3, p->y = 4;
    cout << *p << endl;
    cout << p.use_count() << endl;
    shared_ptr q = p;
    cout << p.use_count() << endl;
    cout << q.use_count() << endl;
    p = nullptr;
    cout << q.use_count() << endl;
    q = nullptr;
    return 0;
}

关键代码的实现:

// 封装一个指针类
class ptr_data {
public:
    ptr_data(A *ptr, int *cnt = nullptr) : ptr(ptr), cnt(cnt) {
        if (this->cnt == nullptr) this->cnt = new int(1); //这里千万不要忘了加this
    }
    void increase_one() {
        *cnt += 1;
        return ;
    }
    void decrease_one() {
        *cnt -= 1;
        if (*cnt == 0) delete ptr;
    }
    bool operator==(const ptr_data &p) {
        return ptr == p.ptr && cnt == p.cnt;
    }
    ~ptr_data() {
        if (*cnt == 0) delete cnt;
    }
    A *ptr;
    int *cnt;
};

// 智能指针主要实现拷贝构造、赋值运算、引用计数、指针的常见功能。
class shared_ptr {
public:
    shared_ptr(A *ptr) : p(ptr) {}
    shared_ptr(const shared_ptr &p) : p(p.p) {
        this->p.increase_one();
    }
    shared_ptr &operator=(const shared_ptr &obj) {
        cout << "copy constructor" << endl;
        if (p == obj.p) return *this;
        p.decrease_one();
        p = obj.p;
        p.increase_one();
        return *this;
    }
    ~shared_ptr() {
        p.decrease_one();
    }
    int use_count() { return *p.cnt; }
    A *operator->() { return p.ptr; }
    A &operator*() { return *p.ptr; }
private:
    ptr_data p;
};

shared_ptr make_shared() {
    return shared_ptr(new A());
}

每日问答聊天机器人:

问题1: c++中delete函数的内在机制是怎么样的?
ChatGPT: c++ 中的 delete 函数是用来释放动态分配的内存的。它有两种形式:

delete p:用于释放单个对象所占用的内存。
delete[] p:用于释放数组对象所占用的内存。
在释放内存之前,delete 会调用对象的析构函数,以便清理对象占用的资源。

delete 的内在机制是通过调用 operator delete 来实现的。operator delete 是一个函数指针,指向用于释放内存的函数。operator delete 的默认实现是系统函数,但是可以被用户自定义。

注意:在使用 delete 释放内存之前,应该确保这块内存是通过 new 运算符动态分配的。否则,可能会出现未定义的行为

问题2: 如果析构函数里什么都没做,对象的内存还会被释放掉吗?
ChatGPT: 是的,即使析构函数中没有任何代码,对象的内存也会被释放掉。析构函数的主要作用是在对象被销毁之前,释放它所占用的资源(如打开的文件、动态分配的内存等)。如果析构函数中没有任何代码,则表示该对象没有任何资源需要释放,所以没有任何影响。

问题3: 将对象赋值为nullptr,系统会自动调用对象的析构函数吗?
ChatGPT: 是的,将对象的指针赋值为 nullptr 会导致系统自动调用该对象的析构函数。如果在析构函数中释放了动态分配的内存或资源,那么在将对象指针赋值为 nullptr 后,这些内存和资源就会被释放。

例如,假设有一个类 MyClass,它的析构函数中释放了动态分配的内存:

class MyClass
{
public:
    MyClass() { /* ... */ }
    ~MyClass() { delete[] m_ptr; }
    // ...
private:
    int* m_ptr;
};

int main()
{
    MyClass* obj = new MyClass;
    // ...
    obj = nullptr;  // 调用析构函数并释放动态分配的内存
}

在上面的代码中,将对象指针赋值为 nullptr 后,系统会自动调用该对象的析构函数,并释放动态分配的内存。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容