01-C++如何管理资源

内存分类

在这里插入图片描述
在这里面我们通常会处理到地方是栈区和堆区。

栈(heap)

是指动态分配内存的区域,这里的内存分配后需要手工释放。这就是栈区存在的一个隐患。而对于这部分内存的分配也有两种方式,newdelete以及mallocfree。通常我们在C语言中采用mallocfree。但是这二者在分配的内存上区分为:

  • newdelete操作区域是free store
  • mallocfree操作区域是heap

但是new和delete的底层是有malloc和free实现的,这也意味着,new和delete也是属于heap区域。
对于堆的应用看一下下面这个例子:

//C++
auto ptr = new std::vector<int>();

但是看起来总是很怪,但是在Java里面:

//Java
ArrayList<int> list = new ArrayList<int>();

在Java里面去没有那么违和了,这种方式通常是由其他语言开发者带到C++里面的。这种方式存在的问题就是,如果忘记了释放空间就会导致内存泄漏。但是有的人会说,我使用一个new然后使用完毕就delete掉,怎么可能忘记呢。

auto ptr = new person();
···
delete ptr;

但是有可能出现两个问题就是:

  1. 在省略号抛出异常;
  2. 分配和释放的不在同一个函数中。
person* fun_1() {
	return new person();
}
void fun_2() {
	auto ptr = fun_1();
	delete ptr;

或许你是函数设计师,你不会忘记本函数内的堆空间的释放。但是你的函数使用者则不一定会记住释放这一空间。
如果我们要在C++里面避免这个问题呢?将这部分数据的从堆区放到栈区

栈区(stack)

是指函数调用过程中产生的本地变量和调用数据的区域.

void foo(int n)
{}

void bar(int n)
{
  int a = n + 1;
  foo(a);
}

int main()
{bar(42);}

在这里插入图片描述
可以发现,变量位于栈上,当函数执行完成后这些内存就会被释放出去。不在需要手动释放。
刚才那个问题就可以迎刃而解:

int fun() {
	person p;
	...
}

一旦p的作用域失去,就可以直接自动释放。无论在省略号的地方是否出现了异常都会自动释放p所占用的空间。

#include<iostream>
using namespace std;
class A {
public:
    A(int x_) : x(x_){cout << "A()" << endl;}
    ~A(){cout << "~A()" << endl;}
    int x;
};
int main() {
    try
    {
        A a_1(1);
        if (a_1.x == 0)
        {
            throw "x == 0";
        }
        A a_2(0);
        if (a_2.x == 0)
        {
            throw "x == 0";
        }
    }
    catch(const char* ex)
    {
        cout << ex << endl;
    }
}

结果
在这里插入图片描述

RAII

使用C++最好把对象存储在栈空间中,但是有一些对象不能放到栈空间。

  • 对象内存很大;
  • 对象大小不能确定;
  • 对象是函数返回值。

最常见的一种情况就是在设计模式当中的工厂模式(factory pattern):

//Phone类
class Phone {
    void make() = 0;
}
//MiPhone类
class MiPhone implements Phone {
public:
    MiPhone() {
        this.make();
    }
    @Override
	void make() {
        // TODO Auto-generated method stub
        System.out.println("make xiaomi phone!");
    }
}
//IPhone类
class IPhone implements Phone {
public:
	IPhone() {
        this.make();
    }
    @Override
   	void make() {
        // TODO Auto-generated method stub
        System.out.println("make iphone!");
    }
}
//PhoneFactory类
class PhoneFactory {
public:
    Phone makePhone(String phoneType) {
        if(phoneType.equalsIgnoreCase("MiPhone")){
            return new MiPhone();
        }
        else if(phoneType.equalsIgnoreCase("iPhone")) {
            return new IPhone();
        }
        return null;
    }
}
//主函数
int main(){
        PhoneFactory factory;
        Phone miPhone = factory.makePhone("MiPhone");            // make xiaomi phone!
        IPhone iPhone = (IPhone)factory.makePhone("iPhone");    // make iphone!
    }
}

这个时候返回的值都是指针不是对象。如何解决这个问题就是,把指针放到一个资源类中,

class shape_wrapper {
public:
  explicit shape_wrapper(shape* ptr = nullptr) : ptr_(ptr){}
  ~shape_wrapper(){delete ptr_;}
  shape* get() const { return ptr_; }
private:
  shape* ptr_;
};

这样就可以在出现异常的时候,或者忘记释放内存的时候;这个资源类的对象会自动调用析构函数,释放所保存的指针的空间。相应在一些资源,比如互斥锁,如果忘记unlock就可能导致线程锁死。
这也是相较于Java的优势来说,C++不需要垃圾回收机制的一个原因。
栈是C++里最"自然"的内存使用方式

Q&A

  1. Q : Thread local的变量存放在那个区?
    A : thread local每一个线程有单独的区域,不需要共享;
  2. Q : delete空指针会报错吗?需要判断释放的指针是否为空
    A : 当然不需要。delete可以自动避免delete空指针;

总结

立一个flag,一周至少写4篇博客,坚持一个月。哈哈!!!

发布了16 篇原创文章 · 获赞 13 · 访问量 4284

猜你喜欢

转载自blog.csdn.net/deng821776892/article/details/105583874