内存分类
在这里面我们通常会处理到地方是栈区和堆区。
栈(heap)
是指动态分配内存的区域,这里的内存分配后需要手工释放。这就是栈区存在的一个隐患。而对于这部分内存的分配也有两种方式,new
和delete
以及malloc
和free
。通常我们在C语言中采用malloc
和free
。但是这二者在分配的内存上区分为:
new
和delete
操作区域是free storemalloc
和free
操作区域是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;
但是有可能出现两个问题就是:
- 在省略号抛出异常;
- 分配和释放的不在同一个函数中。
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
- Q : Thread local的变量存放在那个区?
A : thread local每一个线程有单独的区域,不需要共享; - Q : delete空指针会报错吗?需要判断释放的指针是否为空
A : 当然不需要。delete可以自动避免delete空指针;
总结
立一个flag,一周至少写4篇博客,坚持一个月。哈哈!!!