69-技巧:自定义内存管理

注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。

测试环境:Ubuntu 10.10

GCC版本:9.2.0

 

笔试题一:

            统计对象中某个成员变量的访问次数

分析:

1) 考察面向对象相关的知识

2) 考察内容成员变量的访问次数

 

我的思路:

定义两个成员变量,一个被访问,一个记录次数

缺陷没有考虑种类(只读成员对象)

如果是只读对象,成员变量不能被访问!

 

遗失的关键字

1) mutable(可变)是为了突破const函数的限制而设计的

2) mutable成员变量将永远处于可改变的状态

3) mutable在实际的项目开发中被严禁滥用

4) mutable的深入分析(遗失的原因)

        -    mutable成员变量破坏了只读对象的内部状态

        -    const成员函数保证只读对象的状态不变性

        -    mutable成员变量的出现的无法保证状态不变性

编程实验
成员变量的访问统计
69-1.cpp
#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()
    {
        m_count++;
        
        return m_value;
    }
 
    void setValue(int value)
    {
        m_count++;
        m_value = value;
    }
 
    int getCount() 
    {
        return m_count;
    }
};
 
int main(int argc, char *argv[])
{
    Test t;    
 
    t.setValue(100);    //访问1次
 
    cout << "t.m_value = " << t.getValue() << endl;    //访问2次
    cout << "t.m_count = " << t.getCount() << endl;
 
    return 0;
}

操作:

1) g++ 69-1.cpp -o 69-1.out编译正确,打印结果:

t.m_value = 100
t.m_count = 2    

分析:

        只满足了普通对象情形,但是不满足只读对象。

2) 测试只读对象,代码如下:

#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() 
    {
        m_count++;
        
        return m_value;
    }
 
    void setValue(int value)
    {
        m_count++;
        m_value = value;
    }
 
    int getCount() 
    {
        return m_count;
    }
};
 
int main(int argc, char *argv[])
{
    Test t;
 
    t.setValue(100);
 
    cout << "t.m_value = " << t.getValue() << endl;
    cout << "t.m_count = " << t.getCount() << endl;
 
    const Test ct(200);
    
    cout << "ct.m_value = " << ct.getValue() << endl;
    cout << "ct.m_count = " << ct.getCount() << endl;
    
    return 0;
}

g++ 69-1.cpp -o 69-1.out编译错误:

69-1.cpp: In function ‘int main(int, char**)’:
69-1.cpp:53:41: error: passing ‘const Test’ as ‘this’ argument of ‘int Test::getValue()’ discards qualifiers [-fpermissive]
  cout << "ct.m_value = " << ct.getValue() << endl;
                                         ^
69-1.cpp: 在函数'int main(int, char**)':
69-1.cpp:53:41: 错误:通过'const Test',当前对象('this')'int Test::getValue()'需要丢弃限定词(const)
意味着:const修饰的对象只能调用const函数。

69-1.cpp:54:41: error: passing ‘const Test’ as ‘this’ argument of ‘int Test::getCount()’ discards qualifiers [-fpermissive]
  cout << "ct.m_count = " << ct.getCount() << endl;
                                         ^
69-1.cpp:54:41: 错误:通过'const Test',当前对象('this')'int Test::getValue()'需要丢弃限定词(const)
意味着:const修饰的对象只能调用const函数。

分析:

        const对象只能调用const成员函数。解决办法:用const修饰调用的函数。

代码如下:

#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_count++;
        m_value = value;
    }
 
    int getCount() const
    {
        return m_count;
    }
};
 
int main(int argc, char *argv[])
{
    Test t;
 
    t.setValue(100);
 
    cout << "t.m_value = " << t.getValue() << endl;
    cout << "t.m_count = " << t.getCount() << endl;
 
    const Test ct(200);
    
    cout << "ct.m_value = " << ct.getValue() << endl;
    cout << "ct.m_count = " << ct.getCount() << endl;
    
    return 0;
}

g++ 69-1.cpp -o 69-1.out编译报错:

69-1.cpp: In member function ‘int Test::getValue() const’:
69-1.cpp:19:10: error: increment of member ‘Test::m_count’ in read-only object
   m_count++;
          ^
69-1.cpp: 在成员函数'int Test::getValue() const':
69-1.cpp:19:10: 错误:在只读对象中增加成员变量'Test::m_count'数值

分析:

        在只读对象中不能修改成员变量中的值。

 

3) 怎么解决在只读对象中修改成员变量数值:用mutable关键字

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int m_value;
    mutable int m_count;    //用mutable修饰成员变量
public:
    Test(int value = 0) 
    {
        m_value = value;
        m_count = 0;
    }
 
    int getValue() const
    {
        m_count++;
        
        return m_value;
    }
 
    void setValue(int value)
    {
        m_count++;
        m_value = value;
    }
 
    int getCount() const
    {
        return m_count;
    }
};
 
int main(int argc, char *argv[])
{
    Test t;
 
    t.setValue(100);
 
    cout << "t.m_value = " << t.getValue() << endl;
    cout << "t.m_count = " << t.getCount() << endl;
 
    const Test ct(200);
    
    cout << "ct.m_value = " << ct.getValue() << endl;
    cout << "ct.m_count = " << ct.getCount() << endl;
    
    return 0;
}

g++ 69-1.cpp -o 69-1.out编译正确,打印结果:

t.m_value = 100
t.m_value = 2
ct.m_value = 200
ct.m_count = 1

分析:

        在很多公司并不希望用mutable。

更好的解决办法:

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int m_value;
    int* const m_pCount;//指针常量:初始化后指针存储的地址不能改变,但是数据可变
public:
    //从堆空间申请一块内存,内存数据为0
    Test(int value = 0) : m_pCount(new int(0))
    {
        m_value = value;
    }
 
    int getValue() const
    {
        //操作的是m_pCount指向内存中的数据,不是m_pCount本身
        *m_pCount = *m_pCount + 1;  
        return m_value;
    }
 
    void setValue(int value)
    {
        *m_pCount = *m_pCount + 1;
        m_value = value;
    }
 
    int getCount() const
    {
        return *m_pCount;
    }
 
    ~Test()
    {
         delete m_pCount;
    }
};
 
int main(int argc, char *argv[])
{
    Test t;
 
    t.setValue(100);
 
    cout << "t.m_value = " << t.getValue() << endl;
    cout << "t.m_count = " << t.getCount() << endl;
 
    const Test ct(200);

    cout << "ct.m_value = " << ct.getValue() << endl;
    cout << "ct.m_count = " << ct.getCount() << endl;

    return 0;
}

g++ 69-1.cpp -o 69-1.out编译正确,打印结果:

t.m_value = 100
t.m_value = 2
ct.m_value = 200
ct.m_count = 1

分析:

        定义指针常量(地址不可变,但是地址中的数据可变)。

 

面试题二:

                new关键字创建出来的对象位于什么地方?默认在堆空间,也可以将动态对象创建在静态存储区

 

被忽略的事实

1) new/delete的本质是C++预定义的操作符

2) C++对这两个操作符做了严格的行为定义

        - new

            1. 获取足够大的内存空间(默认为堆空间)

            2. 在获取的空间中调用构造函数创建对象

        - delete

            1. 调用析构函数销毁对象

            2. 归还对象所占用的空间(默认为堆空间)

3) 在C++中能够重载new/delete操作符

        - 全局重载(不推荐)

        - 局部重载(针对具体类进行重载)

    

                重载new/delete的意义在于改变动态对象创建时的内存分配方式

 

4) new/delete的重载方式

//static member function
void* operator new(unsigned int size)    //size代表内存大小
{
    void* ret = NULL;
  
    /* ret point to allocated memory */ 

    return ret;
}

//static member function
void operaotr delete(void* p)
{
    /* free the memory which is pointed by p */
}

 

编程实验
静态存储区中创建动态对象
69-2.cpp
#include <iostream>
#include <string>

using namespace std;

class Test
{
    static const unsigned int COUNT = 4;    //放入符号表
    static char c_buffer[];     //静态字节数组(用来分配静态内存)
    static char c_map[];        //标记每个对象:0-没有被使用 1-被使用
    
    int m_value;
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_buffer + i * sizeof(Test);    //数组首地址+偏移量
                
                cout << "succeed to allocate memory: " << ret << endl;
                
                break;
            }
        }
        
        return ret;
    }
    
    void operator delete (void* p)    //void*为了兼容更多类型指针
    {
        if( p != NULL )    //如果是空指针什么都不处理
        {
            char* mem = reinterpret_cast<char*>(p);
            //p是c_buffer中的元素地址,减去数组c_buffer首地址/Test内存大小,得到数组下标
            int index = (mem - c_buffer) / sizeof(Test);   
            //位置为0,证明内存分配大小合适
            int flag = (mem - c_buffer) % sizeof(Test);    
            //flag=0和index满足数组下标,证明找到要释放的内存下标
            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};//定义数组,长度为4个Test内存长度
char Test::c_map[Test::COUNT] = {0};    //标记数组

int main(int argc, char *argv[])
{
    cout << "===== Test Single Object =====" << endl;
     
    Test* pt = new Test; //调用重载new函数,在静态存储区释放对象
    
    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) g++ 69-2.cpp -o 69-2.out编译正确,打印结果:

=====Test Single Object=====
succeed to allocate memory: 0x804a0cd    
succedd to free memory: 0x804a0cd
=====Test Object Array=====
succeed to allocate memory: 0x804a0cd    
pa[0] = 0x804a0cd    
succeed to allocate memory: 0x804a0d1    
pa[1] = 0x804a0d1    
succeed to allocate memory: 0x804a0d5    
pa[2] = 0x804a0d5        
succeed to allocate memory: 0x804a0d9    
pa[3] = 0x804a0d9            
pa[4] = 0        
delete 0x804a0cd
succeed to free memory: 0x804a0cd
delete 0x804a0d1
succeed to free memory: 0x804a0d1
delete 0x804a0d5
succeed to free memory: 0x804a0d5
delete 0x804a0d9
succeed to free memory: 0x804a0d9
delete 0
   

分析:

        自定义的c_buffer最多存储4个字符,导致pa[4] = 0内存分配失败。

void operator delete (void* p)    //void*为了兼容更多类型指针
{
    if( p != NULL )    //如果是空指针什么都不处理
    {
        char* mem = reinterpret_cast<char*>(p);
        //p是c_buffer中的元素地址,减去数组c_buffer首地址/Test内存大小,得到数组下标
        int index = (mem - c_buffer) / sizeof(Test);   
        //位置为0,证明内存分配大小合适
        int flag = (mem - c_buffer) % sizeof(Test);    
        //flag=0和index满足数组下标,证明找到要释放的内存下标
        if( (flag == 0) && (0 <= index) && (index < COUNT) )    
        {   //释放空间
            c_map[index] = 0;    
            //提示释放成功
            cout << "succeed to free memory: " << p << endl;    
        }
    }
}

index = (mem - c_buffer) / sizeof(Test)    确定c_map[]的索引值
flag = (mem - c_buffer) % sizeof(Test)    确定内存分配为完整对象大小,检查是否有内存碎片

 

面试题三:

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

                    参照面试题二

 

设计思路

1) 解决方案

    -    在类中重载new/delete操作符

    -    在new的操作符重载函数中返回指定的地址

    -    在delete操作符重载中标记对应的地址可用

编程实验
自定义动态对象的存储空间
69-3.cpp
#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

class Test
{
    static unsigned int c_count;    //对比面试题二 
    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);    //对象个数
        //至少创建一个对象,动态创建数组
        ret = (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;
    }
    //在制定内存上创建对象
    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_buffer + i * sizeof(Test);
                    
                    cout << "succeed to allocate memory: " << ret << endl;
                    
                    break;
                }
            }
        }
        else
        {
            ret = malloc(size);    //对比面试题二 
        }
        
        return ret;
    }
    
    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);    //对比面试题二 
            }
        }
    }
};

unsigned int Test::c_count = 0;
char* Test::c_buffer = NULL;    //对比面试题二 
char* Test::c_map = NULL;       //对比面试题二  

int main(int argc, char *argv[])
{
    char buffer[12] = {0};    //对比面试题二 
    
    Test::SetMemorySource(buffer, sizeof(buffer));    //对比面试题二 
    
    cout << "===== Test Single Object =====" << 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) g++ 69-3.cpp -o 69-3.out编译正确,打印结果:

===== Test Single Object =====
succeed to allocate memory: 0xbfc56a80
pa[0] = 0xbfc56a80
succeed to allocate memory: 0xbfc56a84
pa[1] = 0xbfc56a84
succeed to allocate memory: 0xbfc56a88
pa[2] = 0xbfc56a88
pa[3] = 0
pa[4] = 0
delete 0xbfc56a80
succeed to free memory: 0xbfc56a80
delete 0xbfc56a84
succeed to free memory: 0xbfc56a84
delete 0xbfc56a88
succeed to free memory: 0xbfc56a88
delete 0
delete 0

分析:

        以上内容能让我们更好了解new是如何分配内存的。

 

被忽略的事实

1) new[]/delete[]new/delete完全不同

    - 动态对象数组创建通过new[]完成

    - 动态对象数组的销毁通过delete[]完成

    - new[]/delete[]能够被重载,进而改变内存管理方式

 

2) new[]/delete[]的重载方式

//static member function
void* operator new[](unsigned int size)
{
    return malloc(size);
}

//static member function
void operaotr delete[](void* p)
{
    free(p);
}

 

3) 注意事项

    -    new[]实际需要返回的内存空间可能比期望的要多

    -    对象数组占用的内存中需要保存数组信息(多在这)

    -    数组信息用于确定构造函数析构函数的调用次数(保存信息的目的)

编程实验
动态数组的内存管理
69-4.cpp
#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

class Test
{
    int m_value;
public:
    Test()
    {
        m_value = 0;
        cout << "Test()" << endl;
    }
    
    ~Test()
    {
        cout << "~Test()" << endl; 
    }
    
    void* operator new (unsigned int size)
    {
        cout << "operator new: " << size << endl;
        
        return malloc(size);    //malloc返回值为void*
    }
    
    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(int argc, char *argv[])
{
    Test* pt = NULL;
    
    pt = new Test;
    
    delete pt;
    
    pt = new Test[5];
    
    delete[] pt;
    
    return 0;
}

操作:

1) g++ 69-4.cpp -o 69-4.out编译正确,打印结果:

operator new: 4
Test()
~Test()
operator delete: 0x8e5d008
operator new[]: 24
Test()
Test()
Test()
Test()
Test()
~Test()
~Test()
~Test()
~Test()
~Test()
operator delete[]: 0x8e5d018

分析:

        编译器根据size大小,调用多少次构造函数。这就是为什么new和delete成对出现,new[]和delete[]成对出现。对于数组内存分配其实是给每个元素一一分配空间。

 

小结

1) new/delete的本质为操作符

2) 可以通过全局函数重载new/delete(不推荐)

3) 可以针对具体的类重载new/delete

4) new[]/delete[]new/delete完全不同

5) new[]/delete[]也是可以被重载的操作符

6) new[]返回的内存空间可能比期望的要多

©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页