1. 遗失的关键字:mutable
(1)mutable关键字
①mutable是为了突破const函数的限制而设计的
②mutable成员变量将永远处于可改变的状态
③mutable在实际的项目开发中被严禁滥用
(2)深入分析mutable
①mutable成员变量破坏了只读对象的内部状态
②const成员函数保证只读对象的状态不变性
③mutable成员变量的出现无法保证状态不变性
【编程实验】成员变量的访问统计
方法1:利用mutable关键字来实现
#include <iostream> using namespace std; class Test { int m_value; //mutable关键字,表示该变量是可以改变的,哪怕 //在const函数中,也可以被改变! mutable int m_count; // 统计m_value被使用的次数 public: Test(int value=0) { m_value = value; m_count = 0; } //const成员函数目的是让普通对象和const对象都可以访问 int getValue() const //注意,是const成员函数 { ++m_count; //m_count被mutable修饰,表示 //该变量哪怕在const函数中也可被改变! return m_value; } void setValue(int value) { ++m_count; m_value = value; } //const成员函数目的是让普通对象和const对象都可以访问 int getCount() const { return m_count; } }; int main() { Test t; //普通对象 t.setValue(100); //第1次访问t.m_value cout << "t.m_value = " << t.getValue() << endl; //第2次访问t.m_value cout << "t.m_count = " << t.getCount() << endl; //t.m_count = 2; const Test ct(200); //const对象 cout << "ct.m_value = " << ct.getValue() << endl; //第1次访问ct.m_value cout << "ct.m_count = " << ct.getCount() << endl; //ct.m_count = 1; return 0; }
运行结果:
方法2:利用指针或指针常量来实现
#include <iostream> using namespace std; class Test { int m_value; //m_pCount可以是一般的成员变量,而不一定非得是const成员变量 int* const m_pCount; //统计m_value被使用的次数 public: Test(int value=0):m_pCount(new int(0)) { m_value = value; } //const成员函数目的是让普通对象和const对象都可以访问 int getValue() const //注意,是const成员函数 { *m_pCount =*m_pCount + 1; return m_value; } void setValue(int value) { *m_pCount =*m_pCount + 1; m_value = value; } //const成员函数目的是让普通对象和const对象都可以访问 int getCount() const { return *m_pCount; } }; int main() { Test t; //普通对象 t.setValue(100); //第1次访问t.m_value cout << "t.m_value = " << t.getValue() << endl; //第2次访问t.m_value cout << "t.m_count = " << t.getCount() << endl; //t.m_count = 2; const Test ct(200); //const对象 cout << "ct.m_value = " << ct.getValue() << endl; //第1次访问ct.m_value cout << "ct.m_count = " << ct.getCount() << endl; //ct.m_count = 1; return 0; }
运行结果:
2. 被忽略的事实:new关键字创建的对象位于什么地方?
(1)new/delete关键字,其本质是C++预定义的操作符,因此支持重载
(2)C++对这两个操作符做了严格的行为定义
关键字 |
主要行为的定义 |
new |
①获取足够大的内存空间(默认为堆空间) ②在获取的空间中调用构造函数创建对象 |
delete |
①调用析构函数销毁对象 ②归还对象所占用的空间(默认为堆空间) |
(3)C++中new/delete操作符重载的两种方式
①全局重载:会影响所有的类(不推荐)
②局部重载:针对具体类进行重载
(4)重载new/delete的意义:在于改变动态对象创建时的内存分配方式
(5)new/delete的重载示例
//new/delete会被默认的定义为静态成员函数,哪怕在定义时没有显式指出。因为在调用new的时候,对象还 //没创建出来,所以只能通过静态成员函数来调用。 //静态成员函数(即使没写static,也会被自动声明为static函数) void* operator new (unsigned int size) { void* ret = NULL; /*ret指向一片刚分配好的内存*/ return ret; } //静态成员函数 void operator delete(void* p) { /*释放p所指定的内存空间*/ }
(6)注意:
①直接使用new关键字与直接调用Test::operator new(...)函数是有区别的。前者会调用Test类的构造函数,而后者函数调用方式则不会。
②同理,直接使用delete关键字和直接调用Test::operator delete(…)函数时,前者会调用析构函数,后者不会。
【编程实验】静态存储区中创建动态对象
#include <iostream> using namespace std; class Test { //存储在静态区中Test对象的最大数量 static const unsigned int COUNT = 4; //用于存储Test对象的静态内存区,注意为static变量 static char c_buffer[]; //用于标识内存空间的使用情况 static char c_map[]; public: //重载new操作符,将对象分配在静态区(而非堆上!) 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_buffer + i * sizeof(Test); cout << "success to allocate memory: " << ret << endl; break; } } return ret; //当返回后,编译器继继会生成调用构造函数来初始化 //这片内存空间的代码。 } //重载delete操作符,将释放对应的内存空间(标志为空闲) void operator delete (void* p) { if( p != NULL) { char* mem = reinterpret_cast<char*>(p); int index = (mem - c_buffer) / sizeof(Test); int flag = (mem - c_buffer) % sizeof(Test);//传入的是对象地址? if( (flag == 0) && (0 <= index) && (index < COUNT) ) { c_map[index] = 0; //标记为空闲 } cout <<"succeed to free memory: " << p << endl; } //函数返回后,编译器自动生成调用析构函数的代码 } }; char Test::c_buffer[sizeof(Test) * Test::COUNT] = {0}; char Test::c_map[Test::COUNT] = {0}; int main() { cout << "===== Test Single Object =====" << endl; Test* pt = new Test; //相当于(Test*)Test::operator new(sizeof(Test)); delete pt; cout << "===== Test Object Array =====" << endl;
Test* pa[5] = {0}; //模拟内存管理,因内存区最多只能分配4个Test对象,当申请第5个对象 //时,会返回NULL。这个例子也可结合二阶构造来生成《多例模式》 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; }
/*输出结果:
===== Test Single Object =====
success to allocate memory: 0x406038
succeed to free memory: 0x406038
===== Test Object Array =====
success to allocate memory: 0x406038
pa[0] = 0x406038
success to allocate memory: 0x406039
pa[1] = 0x406039
success to allocate memory: 0x40603a
pa[2] = 0x40603a
success to allocate memory: 0x40603b
pa[3] = 0x40603b
pa[4] = 0 //第5个对象分配失败
delete 0x406038
succeed to free memory: 0x406038
delete 0x406039
succeed to free memory: 0x406039
delete 0x40603a
succeed to free memory: 0x40603a
delete 0x40603b
succeed to free memory: 0x40603b
delete 0
*/
【心得体会】
new的两个主要任务:1.分配内存,2.调用构造函数初始化。而上例子中的operator new函数却只见分配内存,不见初始化工作,这看起来有点不可思议。其实重载的operator new函数确实只需完成前一半的功能,那初始化工作在什么时候实现的呢?答案是当调用new Test时,会先调用operator new,之后编译器自动地为我们插入了初始化的代码。这点也可以从Test* pt = new Test和 Test* pt = Test::operator new(...)表现出来的行为不同看出来。前者会调用构造函数,而后者不会,只是一个普通的函数调用。可见operator new并未完全地重载new的两个功能,否则这两个调用结果应该是完全一样的。 这也是new操作符与一般的操作符重载的不同之处。
3.在指定地址上中创建C++对象的解决方案
(1)在类中重载new/delete操作符
(2)在new的操作符重载函数中返回指定的地址
(3)在delete的操作符重载中标记对应的地址可用
【编程实验】自定义动态对象的存储空间
#include <iostream>
#include <cstdlib> //for calloc函数
using namespace std;
class Test
{
//内存中存储Test对象的最大数量
static unsigned int c_count;
//用于存储Test对象的内存区
static char* c_buffer;
//用于标识内存空间的使用情况
static char* c_map;
int m_value;
public:
//设置将对象存储在指定的内存地址处
static bool SetMemorySource(char* memory, unsigned int size)
{
bool ret = false;
c_count = size / sizeof(Test); //计算对象的个数
//calloc会初始化内存为0
ret = ((memory != NULL) && c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));
if (ret)
{
c_buffer = memory;
}
else
{
free(c_map);
c_map = NULL;
c_buffer = NULL;
c_count = 0;
}
return ret;
}
//重载new操作符
void* operator new (unsigned int size)
{
void* ret = NULL;
//1.先在指定的地址空间中分配对象
if(c_count > 0)
{
//查询指定内存是否有空闲
for(int i=0; i< c_count; i++)
{
if(!c_map[i]) //空闲
{
c_map[i] = 1;//标记为正在使用
ret = c_buffer + i * sizeof(Test);
cout << "succeed to allocate memory: " << ret << endl;
break;
}
//空闲己满,直接返回NULL
}
}
else
{
//当没有调用SetMemorySource来要求在指定的地址上分配对象(默认情况)
//此时直接在堆上创建对象。
ret = malloc(size);
cout << "allocate in Heap memory: " << ret << endl;
}
return ret;
}
//重载delete操作符
void operator delete(void* p)
{
if ( p!=NULL )
{
//在指定地址分配对象
if( c_count> 0)
{
char* mem = reinterpret_cast<char*>(p);
int index = (mem - c_buffer) / sizeof(Test);
int flag = (mem - c_buffer) % sizeof(Test);
if( (flag == 0) && (0 <= index) && (index < c_count))
{
c_map[index] = 0; //标记为空闲
cout << "succeed to free memory: " << p << endl;
}
}
else
{
//默认是在堆上创建对象
free(p);
cout << "free Heap memory: " << p << endl;
}
}
}
};
unsigned int Test::c_count = 0;
char* Test::c_buffer = NULL;
char* Test::c_map = NULL;
int main()
{
//默认行为
cout << "==== Default Behavior======" << endl;
Test* pt = new Test; //默认是在堆上创建的
delete pt;
//指定new出来的对象分配在栈上
char buffer[12] = {0};
Test::SetMemorySource(buffer, sizeof(buffer)); //指定在栈上分配对象(最多3个对象)
cout << "==== Test Single Object======" << endl;
pt = new Test; //分配在栈上
delete pt;
cout << "==== Test Object Array======" << endl;
Test* pa[5] = {0};
//分配5个对象,但只有前3个会成功,后面两个返回NULL
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;
}
/*输出结果:
==== Default Behavior======
allocate in Heap memory: 0x5929c8
free Heap memory: 0x5929c8
==== Test Single Object======
succeed to allocate memory: 0x23fe98
succeed to free memory: 0x23fe98
==== Test Object Array======
succeed to allocate memory: 0x23fe98
pa[0] = 0x23fe98
succeed to allocate memory: 0x23fe9c
pa[1] = 0x23fe9c
succeed to allocate memory: 0x23fea0
pa[2] = 0x23fea0
pa[3] = 0
pa[4] = 0
delete 0x23fe98
succeed to free memory: 0x23fe98
delete 0x23fe9c
succeed to free memory: 0x23fe9c
delete 0x23fea0
succeed to free memory: 0x23fea0
delete 0
delete 0
*/
4.new[]和delete[]操作符
(1)new[]/delete[]与new/delete完全不同。可以将new[]和new看作是两个完全不同的的操作符。同理delete[]和delete也是两个完全不同的操作符。
①动态对象数组的创建通过new[]完成
②动态对象数组的销毁通过delete[]完成
(2)new[]/delete[]能够被重载,进而改变内存管理方式(如在堆或静态区中分配内存)
(3)new[]和delete[]的重载示例
//静态成员函数
void* operator new[] (unsigned int size)
{
return malloc(size);
}
//静态成员函数
void operator delete(void* p)
{
free(p);
}
(4)注意事项
①new[]实际需要返回的内存空间可能比期望的要多(因为需额外保存数组长度等信息)
②对象数组占用的内存中需要保存数组信息
③数组信息用于确定构造函数和析构函数的调用次数
④直接使用new[]关键字与直接使用Test::operator new[](...)函数是有区别的。前者会调用Test类的构造函数,而后者函数调用方式则不会。
⑤同理,直接使用delete[]关键字和直接使用Test::operator delete[](…)函数时,前者会调用析构函数,后者不会。
【编程实验】动态数组的内存管理
#include <iostream>
#include <cstdlib> //for malloc函数
using namespace std;
class Test
{
int m_value;
public:
Test()
{
m_value = 0;
cout <<"Test()" << endl;
}
~Test()
{
cout <<"~Test()" << endl;
}
//重载new操作符
void* operator new (unsigned int size)
{
void* ret = NULL;
ret = malloc(size);
cout <<"operator new: " << ret << "size: " <<size << endl;
return ret;
}
//重载delete操作符
void operator delete (void* p)
{
cout <<"operator delete: " << p << endl;
free(p);
}
//重载new[]操作符
void* operator new[] (unsigned int size)
{
void* ret = NULL;
ret = malloc(size);
cout <<"operator new[]: " << ret << " size: " <<size << endl;
return ret;
}
//重载delete[]操作符
void operator delete[] (void* p)
{
cout <<"operator delete[]: " << p << endl;
free(p);
}
};
int main()
{
Test* pt = NULL;
pt = new Test;//注意使用new关键字与直接使用Test::operator new(...)函数是有区别的。
//new Test会调用Test的构造函数,而函数调用方式则不会。
delete pt;
//当用new Test[5]时,只须传入数组元素的个数,编译器会向operator new[](...)函数中
//传入的参数为5*sizeof(Test) + sizeof(unsigned int),其中的sizeof(unsigned int)为额外
//空间,用于保存元素的个数。同理new Test[5]与直接使用Test::operator new[](...)是有区别的。
//前者会调用构造函数,后者则不会。
pt = new Test[5];//相当于Test::operator new(5*sizeof(Test) + sizeof(unsigned int))
delete[] pt;
return 0;
}
/*输出结果:
operator new: 0x5529c8size: 4
Test()
~Test()
operator delete: 0x5529c8
operator new[]: 0x5529c8 size: 24
Test()
Test()
Test()
Test()
Test()
~Test()
~Test()
~Test()
~Test()
~Test()
operator delete[]: 0x5529c8
*/
5. 小结
(1)new/delete的本质为操作符
(2)可以通过全局函数重载new/delete(不推荐)
(3)可以针对具体的类重载new/delete
(4)new[]/delete[]与new/delete完全不同
(5)new[]/delete[]也是可以被重载的操作符
(6)new[]返回的内存空间可能比期望的要多(需额外保存数组长度等信息)