自定义内存管理(五十七)

一个笔试题:编写能统计对象中某个成员变量的访问次数的程序。我们在类中定义一个私有成员变量,在构造函数中初始化为 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()
    {
        m_count++;
        
        return m_Value;
    }
    
    int setValue(int value)
    {
        m_count++;
        
        m_Value = value;
    }
    
    int getCount()
    {
        return m_count;
    }
    
    ~Test()
    {
        
    }
};

int main()
{
    Test t;
    
    t.setValue(100);
    
    cout << "t.m_value = " << t.getValue() << endl;
    cout << "t.m_count = " << t.getCount() << endl;
    
    Test ct(200);
    
    cout << "ct.m_value = " << ct.getValue() << endl;
    cout << "ct.m_count = " << ct.getCount() << endl;
    
    return 0;
}

我们来编译看看结果

我们看到已经正确实现功能了哈,类对象也有可能是 const 的,我们来试试 const 类型的呢

const 对象只能调用 const 成员函数,我们将 getCount 和 getValue 改为 const 成员函数。

我们看到又说 m_count 是在 const 成员函数中不能被改变。那么问题来了,怎样才能改变 const 成员函数中的限制呢?很幸运,在 C++ 中有一个关键字 mutable。mutable 是为了突破 const 函数的限制而设计的,mutable 成员变量将永远处于可改变的状态,它在实际的项目开发中被严禁滥用。我们先来试试,在 m_count 定义前加上 mutable 。

我们看到编译通过,并且成功运行。我们再来看看 mutable 关键字有什么特性,mutable 成员变量破坏了只读对象的内部状态,const 成员函数保证只读对象的状态不变性,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))
    {
        m_Value = value;
    }
    
    int getValue() const
    {
        *m_pCount = *m_pCount + 1;
        
        return m_Value;
    }
    
    int 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;
}

我们定义一个 int* const 类型的指针,然后在构造函数中进行初始化,再利用它进行 ++操作。我们看看编译结果

已经正确实现了哈。下面又是一个很有意思的面试题:new 关键字创建出来的对象位于什么地方?我们大多数人的第一反应是肯定是堆嘛,new 出来的对象肯定在堆上嘛。其实不一定哦。new/delete 的本质是 C++ 预定义的操作符,C++ 对这两个操作符做了严格的行为定义。new:1.获取足够大的内存空间(默认为堆空间);2、在获取的空间中调用构造函数创建对象。delete:1、调用析构函数销毁对象;2、归还对象所占用的空间(默认为堆空间)。那么在 C++ 中是能够重载 new/delete 操作符的,全局重载(不推荐)和局部重载(针对具体类型进行重载)。重载 new/delete 的意义在于改变动态对象创建时的内存分配方式。

下来我们就来利用 new/delete 的重载在静态存储区中创建动态对象。

#include <iostream>
#include <string>

using namespace std;

class Test
{
    static const unsigned int COUNT = 4;
    static char c_buffer[];
    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_buffer + i * sizeof(Test);
                
                cout << "succeed to allocate memory: " << ret << endl;
                
                break;
            }
        }
        
        return ret;
    }
    
    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: " << c_map[index] << endl;
            }
        }
    }
};

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

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 << "pa[" << i << "] = " <<pa[i] << endl; 
    }
    
    for(int i=0; i<5; i++)
    {
        cout << "delet " << pa[i] << endl;
        
        delete pa[i];
    }
    
    return 0;
}

我们在全局数据区定义了 4 个数据类型大小的空间,所以只能申请 4 个数据类型大小的空间。在 main 函数中申请了 5 个数据类型,因此编译器只会分配 4 ,最后一个肯定分配不成功。注意:这都是在全局数据区,而不是在堆上。我们来看看编译结果

我们看到的确是只分配了 4 个 int 类型大小的空间,最后一个为 0,没分配成功。我们已经利用重载 new/delete 操作符,将 new 出来的对象位于全局数据区了,所以 new 出来的对象不一定是只在堆空间中,只是默认在堆空间中。

下来我们再来看一个面试题:如何在指定的地址上创建 C++ 对象?解决方案:a> 在类中重载 new/delete 操作符;b> 在 new 的操作符重载函数中返回指定的地址;c> 在 delete 操作符重载中标记对应的地址可用。下来我们来看看程序怎么写

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

class Test
{
    static unsigned int c_count;
    static char* c_buffer;
    static char* c_map;
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: " << c_map[index] << endl;
                }
            }
            else
            {
                free(p);
            }
        }
    }
};

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

int main()
{
    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 << "delet " << pa[i] << endl;
        
        delete pa[i];
    }
    
    return 0;
}

我们在全局数据区自定义数组 buffer,然后再在 buffer 里进行操作。看看编译结果

我们看到已经正确实现了。还有一个: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()
    {
        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;
}

按照我们之前的想法是 new[5] 肯定是 20了,但是我们今天刚讲了它是需要额外的存储空间来存放用于管理数组信息的空间的,因此申请出来的肯定会比 20 大。我们来看看编译结果

我们看到申请数组申请出来的是 24 个字节的空间。通过对一些经典面试题的学习,总结如下:1、new/delete 的本质为操作符;2、可以通过全局函数重载 new/delete(不推荐),也可以针对具体的类进程重载 new/delete;3、new[] / delete[] 与  new/delete 完全不同;4、new[] / delete[] 也是可以被重载的操作符,new[] 返回的内存空间可能比期望的要多。

欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。

原文地址:http://blog.51cto.com/12810168/2125797

时间: 2024-08-04 06:27:11

自定义内存管理(五十七)的相关文章

C++语言学习(二十)——自定义内存管理

C++语言学习(二十)--自定义内存管理 一.统计类对象中成员变量的访问次数 mutable是为了突破const函数的限制而设计的,mutable修饰的成员变量将永远处于可改变的状态.mutable成员变量破坏了只读对象的内部状态,而const成员函数保证只读对象的状态不变性,因此mutable成员变量无法保证只读对象状态的不变性. #include <iostream> using namespace std; class Test { public: Test():m_count(0) {

第69课 技巧,自定义内存管理

如何统计对象中某个成员变量的访问次数? 解法: 这里我们只能满足普通对象的访问统计,那么const对象呢? 完善解法,使得能统计只读对象的访问次数: 使用了mutable之后,只读对象名存实亡. 1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Test 7 { 8 int m_value; 9 int * const m_pCount; 10 /* mutable int m_

C++中的自定义内存管理

1,问题: 1,new 关键字创建出来的对象位于什么地方? 1,位于堆空间: 2,有没有可能位于其它地方? 1,有: 2,通过一些方式可以使动态创建的对象位于静态存储区: 3,这个存储区在程序结束后释放: 2,new/delete 被忽略的事实: 1,new/delete 的本质是 C++ 预定义的操作符: 1,new/delete 是关键字,但本质是预定义的操作符: 2,C++ 中操作符可以重载: 2,C++ 对这两个操作符做了严格的行为定义: 1,new: 1,获取足够大的内存空间(默认为堆

第69课.技巧:自定义内存管理

1.统计对象中某个成员变量的访问次数 注意:对象(普通对象,只读对象) eg: #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)) { m_value = value; } int getValue() const { *

Linux内存描述之高端内存--Linux内存管理(五)

1. 内核空间和用户空间 过去,CPU的地址总线只有32位, 32的地址总线无论是从逻辑上还是从物理上都只能描述4G的地址空间(232=4Gbit),在物理上理论上最多拥有4G内存(除了IO地址空间,实际内存容量小于4G),逻辑空间也只能描述4G的线性地址空间. 为了合理的利用逻辑4G空间,Linux采用了3:1的策略,即内核占用1G的线性地址空间,用户占用3G的线性地址空间.所以用户进程的地址范围从0~3G,内核地址范围从3G~4G,也就是说,内核空间只有1G的逻辑线性地址空间. 把内核空间和

iOS学习之内存管理

1.1 引用计数 Reference Count 1.2 自动引用计数,ARC(Automatic Reference Counting) 1引用计数 引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式.当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存.

《Effective C++》内存管理

如果global new-hander没有成功配置,会抛出一个std::bad_alloc的exception. #include<iostream> #include<new> #include<climits> using namespace std; template<class T> class NewHandlerSupport{ public: static new_handler set_new_handler(new_handler p);

effective OC2.0 52阅读笔记(五 内存管理)

第五章:内存管理 29 理解引用计数 30 以ARC简化引用计数 总结:ARC通过命名约定将内存管理规则标准化.其他编程语言很少像OC这样强调命名.ARC通过设置全局数据结构(此数据结构的具体内容因处理器而异)中的一个标志位,来代替直接调用autorelease和retain.这是ARC所带来的好处.待编译器与运行期组件日臻成熟,还会出现其他的优化技术.CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFRelease. 31 在dealloc方法中只释放引用

[学习笔记—Objective-C]《Objective-C 程序设计 第6版》第十七章 内存管理和自动计数

本书第十六章讲解的时关于文件和目录的操作,内容相对简单,再次略过. 第十七章的学习笔记经本人归纳整理呈献给大家. 内存管理: - 为了被占用的内存能够再次利用,通过内存管理清理不用的内存.如果一个对象不再使用,就需要释放对象占用的内存 Part 1. 基本内存管理模型 Part 1.1 自动垃圾收集: 系统能够自动贾策对象是否拥有其他的对象,当程序执行需要空间的时候,不再被引用的对象会被自动释放 Part 1.2 手工管理内存计数: 每当创建引用到对象的时候需要为引用数+1.[myFractio