美文网首页
69_技巧:自定义内存管理

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

作者: 编程半岛 | 来源:发表于2018-07-08 13:36 被阅读19次

关键词: mutablenew/deletenew[]/delete[]操作符重载

1. 问题:统计对象中某个成员变量的访问次数。

知识点补充:mutable关键字
(1) mutable 是为了突破const函数的限制而设计的
(2) mutable成员变量永远处于可改变的状态
(3) mutable在实际的项目开发中被严禁滥用

mutable的深入分析
(1) mutable成员变量破坏了只读对象的内部状态
(2) const 成员函数保证只读对象的状态不变性
(3)mutable成员变量的出现无法保证状态不变性

编程说明:成员变量的访问统计——使用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_count++;
        
        m_value = value;
    }
    
    int getCount() const
    {
        return m_count;
    }
};


int main()
{
    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;
}

输出结果:

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

在实际项目中禁止使用mutable关键字,因此可以不使用mutable关键字来统计成员变量的访问。

编程说明:成员变量的访问统计——通过指针常量来统计

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int m_value;
    int* const m_pCount;    // 定义一个指针常量,指针地址不会改变,但指针所指向的值可以改变
public:
    Test(int value = 0) : m_pCount(new int(0))  // 初始化指针所指向的值为0
    {
        m_value = value;
    }
    
    int getValue() const
    {
        *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()
{
    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;
}

输出结果:

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

2. new关键字创建出来的对象位于什么地方?

补充知识点:
(1) new/delete的本质是C++预定义的操作符——可以进行操作符重载
(2) C++对这两个操作符做了严格的行为定义:
new:
1. 获取足够大的内存空间(默认为堆空间)
2. 在获取的空间中调用构造函数创建对象
delete:
1. 调用析构函数销毁对象
2. 归还对象所占用的空间(默认为堆空间)

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

    • 全局重载【不推荐】
    • 局部重载(针对具体类进行重载)
  • 重载new/delete的意义:改变动态对象创建时的内存分配方式

  • newdelete的重载方式
    newdelete的重载函数都是类的成员函数,这两个函数默认为静态成员函数static member function,(函数声明中static关键字可以省略,都已经默认为静态成员函数)

// static member function
void* operator new (unsigned int size)    // size为指定内存空间大小
{
    void* ret = NULL;
    // 1. ret指向内存空间
    // 2. 调用构造函数,创建对象
    return ret;
}

// static member function
void operator delete(void* p)  
{
    // 1. 调用析构函数,销毁对象
    // 2. 归还对象所占用的空间
}

编程说明:在通过new在静态存储区创建对象,重载了newdelete操作符

#include <iostream>

using namespace std;

class Test
{
    /* 在静态存储区里开发动态空间 */
    static const unsigned int COUNT = 4;    // 定义存储对象的个数COUNT 
    static char c_buffer[];     // 定义静态存储区
    static char c_map[];        // 定义标记数组,用于标记自定义的区域哪些位置已经创建对象
    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 + sizeof(Test) * i;  // 将返回值指针指向可用区域
                cout << "succeed to allocate memory: " << ret << endl;
                break;
            }
        }
        
        return ret;
    }

    void operator delete(void* p)
    {
        if( p != NULL )
        {
            char* mem = reinterpret_cast<char*>(p);      // 将需要释放的指针转化为char类型,为下一步的指针运算做准备
            int index = (mem - c_buffer) / sizeof(Test); // 确定区域位置 
            int flag = (mem - c_buffer) % sizeof(Test);  // 通过余数判断此次传来的指针是否合法,若flag==0代表指针合法,若flag!=0代表指针不合法

            if( (flag == 0) && (index >= 0) && (index < COUNT) )    // 判断指针是否合法,且是否在给定区域
            {
                c_map[index] = 0;   // 将指定区域标记为可用

                cout << "succeed to free memory : " << p << endl;
            }
        }
    }
};

char Test::c_buffer[sizeof(Test) * Test::COUNT] = {0};  // 在静态存储区分配所需空间,并将空间初始化为0
char Test::c_map[COUNT] = {0};  // 初始化标记数组,0代表空间未使用,1代表空间已使用

int main()
{
    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 << "p[" << i << "] = " << pa[i] << endl;
    }

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

    return 0;
}

3. 面试题:在指定地址(栈空间/堆空间/静态存储区)上创建c++对象?

解决方案:

  • 在类中重载new/delete操作符
  • new的操作符重载函数中返回指定的地址
  • delete操作符重载中标记对应的地址可用
#include <iostream>
#include <cstdlib>

using namespace std;

/* 动态指定在(静态存储区/栈/堆)空间中创建对象 */
class Test
{
    static char* c_buffer;          // 定义存储区
    static char* c_map;             // 定义标记数组,用于标记自定义的区域哪些位置已经创建对象
    static unsigned int c_count;    // 定义存储对象的个数c_count
    int m_value;
public:
    static bool setMemorySource(char* memory, unsigned int size)    // 动态指定具体内存中创建对象
    {
        bool ret = false;
        
        c_count = size / sizeof(Test);  // 计算空间大小,c_count 
        
        ret = (c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));    // 当c_count>0时, 创建c_map
        if( ret )   // 当 count>0 且 c_map 创建成功时
        {
            c_buffer = memory;      // 将 c_buffer 指定到 memory 上
        }
        else        // 否则, 一切清零
        {
            free(c_map);
            
            c_map = NULL;
            c_buffer = NULL;
            c_count = NULL;
        }
        
        return ret;
    }

    void* operator new (unsigned int size)
    {
        void* ret = NULL;

        if( c_count > 0 )       // c_count>0:即指定的内存空间标记数组长度大于0时,可以在指定的内存空间中创建对象
        {
            for(int i=0; i<c_count; ++i)
            {
                if( !c_map[i] )
                {
                    c_map[i] = 1;       // 将对应区域改为已占用
                    ret = c_buffer + sizeof(Test) * i;  // 将返回值指针指向可用区域
                    cout << "succeed to allocate memory: " << ret << endl;
                    break;
                }
            }
        }
        else
        {
            ret = malloc(size);     // 在堆空间中创建对象    
        }
        
        return ret;
    }

    void operator delete(void* p)
    {
        if( c_count > 0 )   // c_count>0:在指定的内存空间中销毁给定对象
        {
            if( p != NULL )
            {
                char* mem = reinterpret_cast<char*>(p);      // 将需要释放的指针转化为char类型,为下一步的指针运算做准备
                int index = (mem - c_buffer) / sizeof(Test); // 确定区域位置 
                int flag = (mem - c_buffer) % sizeof(Test);  // 通过余数判断此次传来的指针是否合法,若flag==0代表指针合法,若flag!=0代表指针不合法

                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_buffer = NULL;    // 初始化指定内存空间
char* Test::c_map = NULL;       // 初始化标记数组

/* ====== 测试部分 ====== */

/* 在给定堆空间创建对象 */
void test_create_object_in_heap()
{
    cout << "========== test_create_object_in_heap ==========" << endl;
    cout << "===== Test Single Object =====" << endl;
    Test* pt = new Test;
    cout << "pt" << pt << endl;
    cout << "delete pt" << pt << endl;
    delete pt;
    

    cout << "===== Test Object Array =====" << endl;
    Test* pa[5] = {0};
    

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

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

/* 在给定栈空间创建对象 */
void test_create_object_in_stack()
{
    cout << "========== test_create_object_in_stack ==========" << endl;
    
    char space[sizeof(Test) * 4];           // 在栈内存中分配空间
    
    Test::setMemorySource(space, sizeof(space));
    
    cout << "===== Test Single Object =====" << endl;
    Test* pt = new Test;
    cout << "pt" << pt << endl;
    cout << "delete pt" << pt << endl;
    delete pt;
    

    cout << "===== Test Object Array =====" << endl;
    Test* pa[5] = {0};
    

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

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

/* 在给定静态存储区空间创建对象 */
void test_create_object_in_static_area()
{
    cout << "========== test_create_object_in_static_area ==========" << endl;
    
    static char space[sizeof(Test) * 4];        // 在静态存储区分配空间
    
    Test::setMemorySource(space, sizeof(space));
    
    cout << "===== Test Single Object =====" << endl;
    Test* pt = new Test;
    cout << "pt" << pt << endl;
    cout << "delete pt" << pt << endl;
    delete pt;
    

    cout << "===== Test Object Array =====" << endl;
    Test* pa[5] = {0};
    

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

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

int main()
{
    test_create_object_in_heap();
    test_create_object_in_stack();
    test_create_object_in_static_area();

// Test的sizeof为4,那么sizeof代表的值是非静态成员变量的值;
// 在堆空间,栈空间,静态区的内存大小是一致的,只是存放的方式不一致
//  cout << sizeof(Test) << endl;       // 4
    return 0;
}

输出结果:

========== test_create_object_in_heap ==========
===== Test Single Object =====
pt0x9bd3008
delete pt0x9bd3008
===== Test Object Array =====
p[0] = 0x9bd3008
p[1] = 0x9bd3018
p[2] = 0x9bd3028
p[3] = 0x9bd3038
p[4] = 0x9bd3048
delete 0x9bd3008
delete 0x9bd3018
delete 0x9bd3028
delete 0x9bd3038
delete 0x9bd3048
========== test_create_object_in_stack ==========
===== Test Single Object =====
succeed to allocate memory: 0xbfd8a3ec
pt0xbfd8a3ec
delete pt0xbfd8a3ec
succeed to free memory : 0xbfd8a3ec
===== Test Object Array =====
succeed to allocate memory: 0xbfd8a3ec
p[0] = 0xbfd8a3ec
succeed to allocate memory: 0xbfd8a3f0
p[1] = 0xbfd8a3f0
succeed to allocate memory: 0xbfd8a3f4
p[2] = 0xbfd8a3f4
succeed to allocate memory: 0xbfd8a3f8
p[3] = 0xbfd8a3f8
p[4] = 0
delete 0xbfd8a3ec
succeed to free memory : 0xbfd8a3ec
delete 0xbfd8a3f0
succeed to free memory : 0xbfd8a3f0
delete 0xbfd8a3f4
succeed to free memory : 0xbfd8a3f4
delete 0xbfd8a3f8
succeed to free memory : 0xbfd8a3f8
delete 0
========== test_create_object_in_static_area ==========
===== Test Single Object =====
succeed to allocate memory: 0x804b101
pt0x804b101
delete pt0x804b101
succeed to free memory : 0x804b101
===== Test Object Array =====
succeed to allocate memory: 0x804b101
p[0] = 0x804b101
succeed to allocate memory: 0x804b105
p[1] = 0x804b105
succeed to allocate memory: 0x804b109
p[2] = 0x804b109
succeed to allocate memory: 0x804b10d
p[3] = 0x804b10d
p[4] = 0
delete 0x804b101
delete 0x804b105
delete 0x804b109
delete 0x804b10d
delete 0

4. new[]delete[]

  • 动态对象数组创建通过new[]完成
  • 动态对象数组销毁通过delete[]完成
  • new[]/delete[]能够被重载,进而改变内存管理方式
    new[]/delete[]的重载方式
// static member function
void* operator new[] (unsigned int size)    
{
    return malloc(size);
}

// static member function
void operator delete(void* p)  
{
    free(p);
}

注意事项:

  • new[]实际需要返回的内存空间可能比期望的要多
  • 对象数组占用的内存中需要保存数组信息
  • 数组信息用于确定构造函数析构函数的调用次数

编程说明:new/delete与new[]/delete[]调用

#include <iostream>
#include <cstdlib>

using namespace std;

class Test
{
    int m_value;
public:
    Test()
    {
        m_value = 0;
    }
    
    ~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 = NULL;
    pt = new Test;
    delete pt;
    
    pt = new Test[5];
    delete[] pt;
    
    return 0;
}

5. 小结

  • new/delete的本质为操作符
  • 可以通过全局函数重载new/delete(不推荐使用)
  • 可以针对具体的类重载new/delete
  • new[]/delete[]new/delete完全不同
  • new[]/delete[]可以进行操作符重载
  • new[]返回的内存空间可能比期望的要多(包含了数组的长度信息)

相关文章

网友评论

      本文标题:69_技巧:自定义内存管理

      本文链接:https://www.haomeiwen.com/subject/cijroxtx.html