内存管理
C++内存管理
在C语言中,我们想要动态分配内存空间需要使用到malloc,calloc,realloc
函数,在C++中我们同样有动态进行内存管理的方式,并且与C语言中的内存管理有着一些区别。
new/delete
在C++中我们使用new
进行内存的申请,用delete
进行内存的释放。他们的使用比malloc
和free
更加简单方便。
内置类型的内存分配与释放
1 | #include <iostream> |
new
和malloc
一样会在堆上开辟空间同时需要我们手动进行内存的释放,但是new
的写法更加简单易于理解同时我们还可以对单个申请的变量进行初始化。
自定义类型的内存分配与释放
1 | #include <iostream> |
以上这段代码我分别使用malloc
和new
对自定义类型进行内存分配和释放,我们可以发现new
不但可以在分配内存的时候手动调用指定的构造函数还会在分配多个对象的空间时自动调用默认构造函数,delete
也会自动调用析构函数,而malloc
和free
却做不到这一点。因此可以理解为malloc
和free
分配出来的只不过是一个和类一样大小的空间,并不能称作是一个对象,而new
和delete
分配出来的才能被成为对象。
new和delete实现原理
new
和delete
在C++中其实被定义为两个运算符,我们在使用这两个运算符的时候它会在底层调用全局函数operator new
和operator delete
。
operator new/operator delete
我们首先看下这两个函数在底层的实现源码。1
2
3
4
5
6
7
8
9
10
11
12
13
14void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
1 | void operator delete(void *pUserData) |
从源码中能看出的是operator new
和operator delete
在底层也是利用malloc
和free
分配内存的,因此可以说new
和delete
不过是malloc
和free
的一层封装。因此在某些情况下,我们想要用独特的方式给一个类分配内存空间的时候我们就可以重新重载这两个运算符来达到我们的目的。基于这个原理如果有某些类需要特殊的内存分配方式我们可以对其进行运算符的重载。
实现原理
内置类型
对于内置类型来说new
和malloc
,delete
和free
的功能一致,不同的是new[]
和delete[]
才能分配多个连续的空间。
自定义类型
单个元素空间分配
1、new:
1)首先调用operator new
为对象分配空间。
2)调用对象的构造函数对对象进行初始化。
2、delete:
1)调用对象的析构函数进行对象中资源的空间清理。
2)调用operator delete
释放对象的空间。
多个元素空间分配
1、new[]:
1)调用operator new[]
,在operator new[]
中调用operator new
完成N个对象的空间的分配。
2)调用构造函数N次完成N个对象的初始化。
2、delete[]:
1)调用析构函数N次完成N个对象资源的清理。
2)调用operator delete[]
,在operator delete[]
中调用operator delete
完成N个对象的空间的释放。
定位new表达式
当我们用malloc
或者其他方式分配了一块和某个类一样大小的空间给,并用某个指针去指向这块空间。但是问题在于这块空间并未执行构造函数因此并不能称为对象。因此定位new表达式就是为了帮助我们对一块已经分配好的空间执行构造函数使之成为对象的一个方式。
用法
new (place_address) type(initializer-list)。
place_address
为指向某一块空间的指针,type
为自定义类型,initializer-list
为参数列表。
例
1 | #include <iostream> |
这样我们就用定位new表达式给已经分配好的空间调用了构造函数。
常见问题
new和malloc的异同
相同
1、new
和delete
都在堆上进行空间的申请。
2、都需要手动释放空间。
不同
1、malloc
和free
是函数而new
和delete
是运算符。
2、new
可以在分配空间的时候进行初始化。
3、malloc
返回值是void*
需要强转,new
会直接返回与分配空间类型一样的类型指针。
4、malloc
需要手动计算分配空间大小在进行传入,而new
只需要类型和元素个数,空间大小会自动计算。
5、new
在给自定义类型分配空间的时候会自动调用其构造函数,delete
会自动调用其析构函数。
6、malloc
申请空间失败会返回NULL
,new
会抛异常。
7、new
和delete
是malloc
和free
的一层封装,因此效率会低一些。
写一个只能在堆上创建对象的类
思路
1、将构造函数,赋值构造函数全部封装为私有,不允许外部直接调用构造。
2、单独写一个静态函数提供在堆上创建对象的接口。
实现
1 | #include <iostream> |
写一个只能在栈上创建对象的类
思路一
1、将构造函数,赋值构造函数全部封装为私有,不允许外部直接调用构造。
2、单独写一个静态函数提供在栈上创建对象的接口。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31#include <iostream>
using namespace std;
class StackOnly
{
public:
static StackOnly Create()
{
//创建匿名对象
return StackOnly();
}
static StackOnly a;
~StackOnly()
{
cout << "destorying" << endl;
}
private:
StackOnly()
{
cout << "create in stack" << endl;
}
StackOnly(const StackOnly&){}
};
int main()
{
StackOnly::a = StackOnly::Create();
}
[misaki@localhost 第三章-内存管理]$ ./StackOnlyClass
create in stack
destorying
思路二
1、直接将operator new
和operator delete
重载并定义为私有。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#include <iostream>
using namespace std;
class StackOnly
{
public:
StackOnly()
{
cout << "create in stack" << endl;
}
~StackOnly()
{
cout << "destorying" << endl;
}
private:
void* operator new(size_t size){}
void operator delete(void* p){}
};
int main()
{
StackOnly p;
}
实现单例模式
单例模式是一种设计模式,在实战中经常会用到。其意思是创建一个类这个类只能唯一的创建一个对象,如果之后还想用这个类创建新对象的时候都会返回最开始创建的呢个对象。
要实现这一点有两种思路,分别成为懒汉模式和饿汉模式。
饿汉模式
饿汉模式是在程序启动时就夹在所有需要资源的设计模式,用这种思想实现单例模式时需要在程序一开始就直接声明对象,需要适用对象就返回对象即可。但是坏处是程序启动时会消耗时间可能会造成程序启动缓慢。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#include <iostream>
class Singleton1
{
public:
//外留接口
static Singleton1* GetInstence()
{
return &_singleton;
}
private:
//防拷贝,禁用构造,拷贝构造和赋值
Singleton1()
{
}
Singleton1(const Singleton1&);
Singleton1& operator=(const Singleton1&);
static Singleton1 _singleton;
};
Singleton1 Singleton1::_singleton;
懒汉模式
1 | class Singleton2 |
懒汉模式就是在第一次使用时才会创建对象,因此我们需要对变量进行判断考虑到线程安全我们需要对其加锁。懒汉模式的坏处是可能造成程序运行卡顿。