69、自定义内存管理

1、关键字 mutable

这个代码会出错,因为p没有初始化

#include <iostream>

using namespace std;

int main()
{
    int* p;
    (*p) = 1;
    cout << *p << endl;
    return 0;
}

  • 笔试题
    统计对象中某个成员变量的访问次数
#include <iostream>
#include <string>

using namespace std;

class Test
{
	int m_value;
	int m_count;
public:
	Test(int value = 0)
	{
		m_value = value;
		m_count = 0;
	}

	int getValue()const
	{
		m_count++;
		return m_value;
	}

	void setValue(int value)
	{
		m_value = value;
		m_count++;
	}

	int getCount()const
	{
		return m_count;
	}
};


int main()
{
	Test t(1);
	t.setValue(100);
	cout << "t.m_value = " << t.getValue() << endl;
	cout << t.getCount() << endl;

	const Test t2(20);
	cout << "t2.m_value = " << t2.getValue() << endl;
	cout << t2.getCount() << endl;
	return 0;
}

这个程序的问题在于可以解决普通对象中某个成员变量的访问次数问题,但是无法解决只读对象中成员变量的访问次数问题。在代码的第 19 行,我们在只读成员函数里面是不能修改成员变量的值的,于是,这样的写法肯定是不对的。

  • 遗失的关键字
    — mutable 是为了突破 const 函数的限制而设计的
    — mutable 成员变量将永远处于可改变的状态
    — mutable 在实际的项目开发中被严禁滥用
  • mutable 的深入分析
    — mutable 成员变量破坏了只读对象的内部状态
    — const 成员函数保证只读对象的状态不变性
    — mutable 成员变量的出现无法保证状态不变性
#include <iostream>
#include <string>

using namespace std;

class Test
{
	int m_value;
	mutable int m_count;
public:
	Test(int value = 0)
	{
		m_value = value;
		m_count = 0;
	}

	int getValue()const
	{
		m_count++;
		return m_value;
	}

	void setValue(int value)
	{
		m_value = value;
		m_count++;
	}

	int getCount()const
	{
		return m_count;
	}
};


int main()
{
	Test t(1);
	t.setValue(100);
	cout << "t.m_value = " << t.getValue() << endl;
	cout << t.getCount() << endl;

	const Test t2(20);
	cout << "t2.m_value = " << t2.getValue() << endl;
	cout << t2.getCount() << endl;
	return 0;
}

在这里插入图片描述
使用 mutable 可以解决我们的疑惑,但是建议不要这样用。


建议的用法:满足普通对象和只读对象的用法(这个代码存在问题,有一个函数没写成const)

#include <iostream>
#include <string>

using namespace std;

class Test
{
	int m_value;
	int* const m_Pcount;
	//mutable int m_count;
public:
	Test(int value = 0):m_Pcount(new int(0))
	{
		m_value = value;
		//m_count = 0;
	}

	int getValue()const
	{
		(*m_Pcount)++;
		//m_count++;
		return m_value;
	}

	void setValue(int value)
	{
		m_value = value;
		//m_count++;
		(*m_Pcount)++;
	}

	int getCount()const
	{
		//return m_count;
		return *m_Pcount;
	}
};

int main()
{
	Test t(1);
	t.setValue(100);
	cout << "t.m_value = " << t.getValue() << endl;
	cout << t.getCount() << endl;

	const Test t2(20);
	cout << "t2.m_value = " << t2.getValue() << endl;
	cout << t2.getCount() << endl;
	return 0;
}

在这里插入图片描述
或者这种写法:

#include <iostream>

using namespace std;

class Test
{
private:
    int m_value;
    int* p_cnt;
public:
    Test()
    {
        m_value = 0;
        p_cnt = new int(0);
    }

    void setValue(int value)
    {
        m_value = value;
        (*p_cnt)++;
    }

    int getValue()const
    {
        (*p_cnt)++;
        return m_value;
    }

    int getCnt()const
    {
        return *p_cnt;
    }

};

int main()
{
    Test t;
    t.setValue(10);
    cout << t.getValue() << endl;
    cout << t.getCnt() << endl;

    cout << endl;

    const Test tt;
    cout << tt.getValue() << endl;
    cout << tt.getCnt() << endl;

    return 0;
}
#include <iostream>

using namespace std;

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int m_value;
    mutable int m_count;
public:
    Test(int value = 0)
    {
        m_value = value;
        m_count = 0;
    }

    int getValue()const
    {
        m_count++;
        return m_value;
    }

    void setValue(int value)const
    {
        int* p = const_cast<int*>(&m_value);
        *p = value;
        m_count++;
    }

    int getCount()const
    {
        return m_count;
    }
};


int main()
{
    Test t(1);
    t.setValue(100);
    cout << "t.m_value = " << t.getValue() << endl;
    cout << t.getCount() << endl;

    const Test t2(20);
    cout << "t2.m_value = " << t2.getValue() << endl;
    cout << t2.getCount() << endl;
    return 0;
}



2、面试题2

new 关键字创建出来的对象位于什么地方?一定只是在堆空间上吗?

  • 被忽略的事实
    new/delete 的本质是C++预定义操作符(那么就可以进行操作符重载)
    — C++ 对这两个操作符做了严格的行为定义
  • new
    1、获取足够大的内存空间(默认为堆空间)
    2、在获取的空间中调用构造函数创建对象
  • delete
    1、调用析构函数销毁对象
    2、归还对象所占用的空间(默认为堆空间)
  • 在C++中能够重载new/delete操作符
    — 全局重载(不推荐)
    — 局部重载(针对具体类进行重载)
    重载 new/delete 的意义在于改变动态对象创建时的内存分配方式
  • new/delete 的重载方式
    在这里插入图片描述
    这两个重载的函数默认都是静态成员函数,无论你加不加static,它都是静态的。

全局数据区和栈的数据分布不一样。栈是向下生长,下一个地址值比上一个地址值小;全局数据区向上,下一个地址值比上一个地址值大。

#include <iostream>
#include <string>
using namespace std;

class Test
{
	static const unsigned int COUNT = 4;
	static char c_buf[];
	static char c_map[];

public:
	void* operator new(unsigned int size)
	{
		void* ret = NULL;
		for (int i = 0; i < COUNT; i++)
		{
			if (!c_map[i])
			{
				c_map[i] = 1;
				ret = c_buf + i*sizeof(Test);
				cout << "succeed to allocate memory: " << ret << endl;
				break;
			}
		}
		return ret;
	}
	void operator delete(void* p)
	{
		char* mem = reinterpret_cast<char*>(p);
		int index = (mem - c_buf) / sizeof(Test);
		int flag = (mem - c_buf) % sizeof(Test);
		if ((flag == 0) && (index >= 0) && (index < COUNT))
		{
			c_map[index] = 0;
			cout << "succeed to free memory: " << p << endl;
			//遇到的问题:直接打印mem为什么打印不出来,因为char* 在cout 下会从该地址打印字符串,如果是输出指针的地址统一转换成 void*是最好的。
		}
	}
};

char Test::c_buf[sizeof(Test) * Test::COUNT] = {0};
char Test::c_map[COUNT] = {0};

int main()
{
	cout << "===== Test Object Array =====" << endl;

	Test* pt = new Test;

	delete pt;

	cout << "===== Test Object Array =====" << endl;

	Test* pa[5] = { 0 };

	for (int i = 0; i < 5; i++)
	{
		pa[i] = new Test;
		cout << "pa[" << i << "] = " << pa[i] << endl;
	}

	for (int i = 0; i < 5; i++)
	{
		cout << "delete " << pa[i] << endl;
		delete pa[i];
	}

	return 0;
}

在这里插入图片描述
这个程序告诉我们new出来的对象不一定是在堆空间上,在上面这个程序,new出来的对象就是在c_buf这个静态数组里面。

其实这个程序我一开始理解起来是非常困难的,还好我大哥一直在指点我。我不理解的地方主要是不知道为什么要定义c_mapc_map的本质就是作为标记使用。c_buf的本质就是存储内存空间。重载的new这个重载函数的 void 指针指向c_buf的首地址。c_map如果存满了,那么c_buf肯定就存满了,那么这个标志可以让c_buf不存储了。

重载的delete这个重载函数,因为c_buf里面共有 COUNT * sizeof(Test)个元素,所以在计算个数index 的时候要除以 sizeof(Test),判断他在 new 第几个对象。delete完成的标志就是把 c_map这个标志设置为 0。

这个方法 + 二阶构造 = n例模式,使得我们最多只能存在n个对象。

3、面试题3

如何在指定的地址上创建 C++ 对象?

  • 解决方案
    — 在类中重载 new/delete操作符
    — 在 new的操作符重载函数中返回指定的地址
    — 在delete操作符重载中标记对于的地址可用

程序:在栈上动态创建对象

#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;

class Test
{
	static unsigned int c_count;
	static char* c_buf;
	static char* c_map;

	int m_value;
public:
	static bool SetMemorySource(char* memory, unsigned int size)
	{
		bool ret = false;
		c_count = size / sizeof(Test);
		ret = (c_count  && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));
		if (ret)
		{
			c_buf = memory;
		}
		else
		{
			free(c_map);

			c_map = NULL;
			c_buf = NULL;
			c_count = 0;
		}
		return ret;
	}

	void* operator new(unsigned int size)
	{
		void* ret = NULL;
		if (c_count > 0)
		{
			for (int i = 0; i < c_count; i++)
			{
				if (!c_map[i])
				{
					c_map[i] = 1;
					ret = c_buf + i*sizeof(Test);
					cout << "succeed to allocate memory: " << ret << endl;
					break;
				}
			}
		}
		else
		{
			ret = malloc(size);
		}
		return ret;
	}
	void operator delete(void* p)
	{
		if (c_count > 0)
		{
			if (p != NULL)
			{
				char* mem = reinterpret_cast<char*>(p);
				int index = (mem - c_buf) / sizeof(Test);
				int flag = (mem - c_buf) % sizeof(Test);
				if ((flag == 0) && (index >= 0) && (index < c_count))
				{
					c_map[index] = 0;
					cout << "succeed to free memory: " << p << endl;
				}
			}
		}
		else
		{
			free(p);
		}
	}

};

unsigned int Test::c_count = 0;
char* Test::c_buf = NULL;
char* Test::c_map = NULL;

int main()
{
	char buffer[12] = { 0 };

	Test::SetMemorySource(buffer, sizeof(buffer));

	cout << "===== Test Object Array =====" << endl;

	Test* pt = new Test;

	delete pt;

	cout << "===== Test Object Array =====" << endl;

	Test* pa[5] = { 0 };

	for (int i = 0; i < 5; i++)
	{
		pa[i] = new Test;
		cout << "pa[" << i << "] = " << pa[i] << endl;
	}

	for (int i = 0; i < 5; i++)
	{
		cout << "delete " << pa[i] << endl;
		delete pa[i];
	}

	return 0;
}

在这里插入图片描述
解析:这个程序也很经典,可以使动态空间在栈上创建。并且限定个数。
主要有两点:
1、 SetMemorySource(char* memory, unsigned int size)这个函数使得 c_buf 和 c_map 重新有了作用。c_map的本质就是作为标记使用,c_buf的本质就是存储内存空间。c_map这个指针是指向我们 realloc 的一段空间,c_buf这个指针是指向我们指定的空间,c_buf指向的空间大于等于c_map指向的空间,之间的大小差距小于或等于一个类类型的大小。本质等同于上一段程序。
如果我们没有指定内存,就按默认的在堆上创建空间。

2、new 和 delete 的重载。

4、被忽略的事实4

  • new[]/delete[]new/delete完全不同
    — 动态数组创建通过 new[] 完成
    — 动态数组的销毁通过 delete[] 完成
    new/delete能够被重载,进而改变内存管理方式
  • new/delete的重载方式
    在这里插入图片描述
  • 注意事项
    new[]实际需要返回的内存空间肯比期望的要多
    — 对象数组占用的内存中需要保存数组信息
    — 数组信息用于确定构造函数和析构函数的调用次数
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;

class Test
{
	int m_value;
public:
	Test(int value = 0)
	{
		m_value = value;
	}

	~Test()
	{

	}

	void* operator new(unsigned int size)
	{
		cout << "operator new: " << size << endl;
		return malloc(size);
	}

	void operator delete(void* p)
	{
		cout << "operator delete: " << p << endl;
		free(p);
	}

	void* operator new[](unsigned int size)
	{
		cout << "operator new[]: " << size << endl;
		return malloc(size);
	}

		void operator delete[](void* p)
	{
		cout << "operator delete[]: " << p << endl;
		free(p);
	}

};


int main()
{
	Test* pt = new Test(1);
	delete pt;

	Test* pa = new Test[5];
	delete[] pa;
	return 0;
}

在这里插入图片描述
4*5 = 20,按理说是20个字节,系统多给4个字节保存长度信息。所以new[] 的空间只能delete[] ,如果delete并不能释放完全。

小结

  • new/delete 的本质为操作符
  • 可以通过全局函数重载 new/delete
  • 可以针对具体的类重载 new/delete
  • new[]/delete[]new/delete完全不同
  • new[]/delete[]也是可以被重载的操作符
  • new[]返回的内存空间可能比期望的要多
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值