动态内存管理与智能指针

C、 C++中编译内存分配:

//一个 C、 C++程序编译时内存分为 4大存储区:栈区、堆区、数据段、程序代码段。
代码段:    又称为常量区,储存常量和编译之后的代码指令
数据段:    又称为静态区,存放全局变量和static 修饰的变量
堆区:      即动态内存分配。程序在运行的时候用 malloc 或 new 申请任意大小的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。 如果在堆上分配了空间,就有责任回它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
栈区:      在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。   

在C语言中,我们开辟空间一般都是通过malloc、free来完成的

malloc
     //开辟一块指定大小的空间返回值为void*,不会对已开辟的空间进行初始化
free
     //清理空间

在c++中,动态内存的管理是通过一对运算符来完成的:

new
  //在动态内存中为对象分配空间并返回一个指向该对象的指针,我们并可以选择对对象初始化
delete
  // 接受一个动态对象的指针,销毁该对象,并释放与之关联的内存

malloc/free和new/delete区别

1、new/delete为操作符,可以重载,只能在c++中使用,malloc/free为函数,可以覆盖,在c/c++都可使用
2、new为类对象开辟空间可以调用构造函数进行初始化,malloc不会
3、new开辟空间时不用计算空间大小,可直接给对象或类型,malloc要给出确定的字节数
4、new返回的是某种数据类型的指针,而malloc返回为void的指针

new/delete和new[]/delete[] 底层实现原理

new
     //在开辟空间构造类对象时,底层调用operator new(size=sizeof())-------->while {   malloc (size)}----------->再去调用构造函数初始化对象
     operator new在分配内存失败会调用new_handler尝试让系统释放内存,继续申请,如果系统内存紧张,就会陷入死循环。则要么申请到内存,要么陷入死循环
new[]
      //开辟一段连续的空间,底层调用:operator newp[](size = sizeof())----------->operator new---------->while{malloc()}-------->N次构造函数进行初始化
当显式定义了析构函数时,利用new[] 会多开辟四个字节(存放在对象地址的上面)的空间存放调用构造函数的个数N,以方便在析构时使用
当使用默认析构时,开辟的空间数为实际需要的大小
new/delete和new[]/delete[]和malloc/free 一定要搭配使用
否则会出现程序崩溃或者内存泄露。例如new/delete[](显式定义了析构)--程序崩溃。new[]/free(显式定义析构)--内存泄露

动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是相当困难的,有时我们会忘记释放内存,在这种情况下会产生内存泄漏,有时在尚有指针引用内存的情况下我们就释放了它,在这种情况下就会产生引用非法内存的指针。

为了更容易的使用动态内存,新的标准库提出了两种智能指针来管理动态对象,智能指针的行为类似于常规指针,重要的区别是,它负责自动释放所指向的对象,在新的标准库中提出了一种思想
RAII(资源分配与初始化),这是一种规范,一种解决问题的思想,类似于定义一个类,构造函数(资源分配与初始化),析构函数(清理资源与释放空间)
智能指针就是RAII的一种应用,智能的管理着指针释放的问题
    :1、新开辟出来的资源交给智能类的对象(在构造函数中保存资源)
	,2、对象要像指针一样去解引用  operator*()
	,3、对象要像指针一样访问成员  operator->()

模拟实现AutoPtr----->各大公司禁止使用(带有缺陷的指针)
只能对单个对象进行管理,每次只有一个对象可使用申请的空间,当资源转移给另一个对象使用时,前一个对象就会赋值为空。(管理权的转移)

template <class T>
class Autoptr
{
public:
	Autoptr(T* str)
		:_str(str)
	{}
	Autoptr( Autoptr<T>&ap)
		:_str(ap._str)
	{
		ap._str = NULL;
	}
	Autoptr<T>& operator=( Autoptr<T>&ap)
	{
		if (this != &ap)
		{
			delete _str;
			_str = ap._str;
			ap._str = NULL;
		}
		return *this;
	}
	T& operator*()
	{
		 return *_str;
	}
	T* operator->()
	{
		return _str;
	}
	~Autoptr()
	{
		delete _str;
	}
private:
	T* _str;
};

模拟实现ScopedPtr---->推荐使用,资源独占(防拷贝)
与 auto_ptr 一样,都是管理单个对象的, 它的作用是:在一个类中防止拷贝,或许我们可以认为,把它定义为私有的,其实并不然,因为访问私有成员或函数的方式还是会调用函数声明,在这里,我们学习一种新的防拷贝方式:只声明不定义,且将拷贝构造与赋值运算符重载声明为私有即可 ,ScopedPtr 的实现机制体现了它的独占性,当一个对象占用空间时, 其他对象将无法使用,并且实现不了资源的转移。

#include <iostream>
using namespace std;
template <class T>
class ScopedPtr
{
public:
	ScopedPtr(T* str)
	:_str(str)
	{}
	T& operator*()
	{
		return *_str;
	}
	T* operator->()
	{
		return _str;
	}
	~ScopedPtr()
	{
		delete _str;
	}
private:
	ScopedPtr(const ScopedPtr<T>&ap);
	ScopedPtr<T>& operator=(const ScopedPtr<T>& ap);
	T* _str;

};

模拟实现SharePtr----共享资源,利用引用计数

#include <iostream>
using namespace std;
template <class T>
class SharePtr
{
public:
	SharePtr(T* str)
		:_str(str)
		, _count(new int(1))
	{

	}
	SharePtr(SharePtr<T>& ap)
		:_str(ap._str)
		, _count(ap._count)
	{
		(*_count)++;
	}
	SharePtr<T>& operator=(SharePtr<T>& ap)
	{
		assert(ap._str);
		if (this != &ap)//判断是不是给自己赋值
		{
			if (_str == NULL)  //为空,直接赋值
			{
				_str = ap._str;
				_count = ap._count;
			}
			else if (_str&& (*_count== 1)) //为一个引用计数,
			{
				delete _str;
				_str = NULL;
				_str = ap._str;
				_count = ap._count;
			}
			else
			{
				(*_count)--;
				_str = ap._str;
				_count = ap._count;
        	}
			/*
			if (_str&& (*_count== 1))
			    delete _str;
			else if(_str == NULL)
			           ;
		    else
			     (*_count)--;

			_str = ap._str;
		    _count = ap._count;
			*_count = ++(*ap._count);
			*/
		}
		return *this;
	}
	T& operator*()
	{
		return *_str;
	}
	T* operator->()
	{
		return _str;
	}
	~SharePtr()
	{
		if ((*_count == 1) && _str)
		{
			delete _str;
			_str = NULL;
		}
	}
	
private:
	
	T* _str;
	int* _count;

};

每个shareptr都有一个关联的计数器,无论何时我们拷贝一个shareptr,计数器都会递增,例如,当用一个对象去初始化另一个对象时,或者将其作为参数传递给一个函数,以及作为函数的返回值,他所关联的计数器都会递增,当我们给shareprt赋予一个新值,或者shareptr被销毁时,计数器就会递减,一旦有一个计数器变为了0,就会自动释放(通过析构函数来完成收尾工作)所管理的内存。

WeakPtr:一种弱引用,不支持RAII ,并不会去管理内存资源的释放,只为解决循环引用而生

假如两个 SharePtr<int>*  类型的指针   
  A 指向 B            B 指向A
  当你想要释放其中任何一个时,都无法完成操作
  这时就可以借助WeakPtr
  WeakPtr 的使用要借助 SharePtr来进行对象的初始化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值