new/delete完美内存释放

本文探讨了C++中new/delete操作的工作原理,包括内存分配和构造/析构过程。通过实例展示了不当使用new可能导致的内存泄漏问题,并提出了常见的解决方案。此外,介绍了STL如何通过allocator避免直接使用new/delete,以及如何通过自定义模板类实现完美内存释放,确保析构函数在释放内存前被正确调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、new/delete     

    在c++中新增了new/delete关键字,当我们定义了一个类Class Test;时,auto  test = new TestNew() 这条语句将会执行Test的构造函数默认构造函数,例如:

class TestNew
{
public:
	TestNew()
	{
		_i = 0;
		_c = 0;
		std::cout << _i << _c;
	}

	~TestNew()
	{

	}

private:
	int   _i;
	float _c;
};

int main()
{
	auto test_new = new TestNew();
	return 0;
}

     实际上  new和delete操作都分为两步,new 操作首先 malloc sizeof(TestNew)大小的内存空间,再去执行TestNew()构造函数。同时delete test_new 操作是先执行~TestNew()操作,再执行free(test_new)操作。 所以导致了一些问题的出现,举例如下:

class TestNew
{
public:
	TestNew()
	{
		_i = new int[10];
		_c = 0;
		std::cout << "TestNew()" << std::endl;
	}

	~TestNew()
	{
        if (_i != nullptr)
        {
            delete[] _i;
            _i = nullptr;
        }
        std::cout << "~TestNew()" << std::endl;
	}

private:
	int   *_i;
	float _c;
};

int main()
{
    void *p = new TestNew();
    delete p;
	return 0;
}

运行结果:

    该代码用void* 指针去指向 new TestNew(), 导致调用的构造函数,而没有调用析构函数,同时构造函数中分配的内存,在析构函数中没有正确执行,导致了内存泄漏。

二、常见用法

    而如下结构是经常用的:

struct Data
{
    ~Data()
    {
        delete _data;
        _size = 0;

    }
    void *_data; // 指针
    int   _size; // 大小
};


template<class T>
void AddData(std::list<Data*> &list_data)
{
    void* t = new T();
    Data *data = new Data();
    data._data = t;
    data._size = sizeof(T);
    list_data.push_back(data);
}

int main()
{
    std::list<Data*> list_data;
    AddData<TestNew>(list_data);
    AddData<int>(list_data);
    AddData<double>(list_data);

    for (auto iter = list_data.begin(); iter != list_data.end(); iter++)
    {
        delete *iter;
    }

	return 0;
}

实现一个可以增加任何数据的list,此时int和double是可以正常释放的,而TestNew是不能释放成功,能够正常释放的空间只有

sizeof(TestNew);如何解决这个问题成为不定参数列表的关键。事实上stl中的内存分配都不通过new/delete来进行,因为这本来就两步操作,stl分配方式如下:

  1. allocator与类绑定,因为allocator是一个泛型类
  2. allocate()申请指定大小空间
  3. construct()构建对象,其参数为可变参数,所以可以选择匹配的构造函数
  4. 使用,与其它指针使用无异
  5. destroy()析构对象,此时空间还是可以使用
  6. deallocate()回收空间

虽然增加了一步,但大体流程还是先分配内存,再构造对象。

三、解决方案(2019.6.26增加)

       既然是因为无法调用到析构函数导致内存泄漏,所有我们可以采取手动去调用析构函数的方法,定义一个泛型类,包装需要构造的函数,同时增加一个头指针,指向析构函数,在消耗这个对象时,可以先手动执行析构函数,再释放内存(具体实现待添加...)

template<class T>
struct data_construct_child;
struct data_construct
{
    template<class T>
    static int size()
    {
        return sizeof(T) + sizeof(void*);
    }

    template<class T>
    static void* construct(const T& data, void *memory = nullptr)
    {
        if (memory == nullptr) memory = new char[size<T>()];

        *((void **)memory) = data_construct_child<T>::get_instance();

        char *p_data = (char*)memory + sizeof(void*);
        new (p_data) T(data);
        return (void*)memory;
    }

    template<class T>
    static T& get_value(void *p)
    {
        T *t = (T*)((char*)p + sizeof(void*));
        return *t;
    }

    static void destruct(void *memory, bool delete_memory = true)
    {
        data_construct *constructs = (data_construct *)*((void**)memory);
        constructs->call_destruct((char*)memory + sizeof(void*));
        if (delete_memory) delete[]  memory;
    }

    virtual void call_destruct(void *p) = 0;
};

template<class T>
struct data_construct_child : public data_construct
{
    static data_construct_child<T> * get_instance()
    {
        static data_construct_child<T> instance;
        return &instance;
    }

    virtual void call_destruct(void *p)
    {
        T *t = (T*)p;
        t->~T();
    }

};

class TestNew
{
public:
	TestNew()
	{
		_i = new int[10];
		_c = 0;
		std::cout << "TestNew()" << std::endl;
	}

    TestNew(const TestNew & test_new)
    {
        _c = test_new._c;
        _i = new int[10];
        for (int i = 0; i < 10; i++)
        {
            _i[i] = test_new._i[i];
        }
        std::cout << "TestNew(TestNew &t)" << std::endl;
    }

	~TestNew()
	{
        if (_i != nullptr)
        {
            delete[] _i;
            _i = nullptr;
        }
        std::cout << "~TestNew()" << std::endl;
	}

private:
	int   *_i;
	float _c;
};

struct Data
{
    ~Data()
    {
        data_construct::destruct(_data);
        _size = 0;
    }
    void *_data; // 指针
    int   _size; // 大小
};


template<class T>
void AddData(std::list<Data*> &list_data)
{
    T t;
    void* pp = data_construct::construct<T>(t);
    auto data = new Data();
    data->_data = pp;
    data->_size = data_construct::size<T>();
    list_data.push_back(data);
}

int main()
{
    std::list<Data*> list_data;
    AddData<TestNew>(list_data);
    AddData<int>(list_data);
    AddData<double>(list_data);

    for (auto iter = list_data.begin(); iter != list_data.end(); iter++)
    {
        delete *iter;
    }

	return 0;
}

         通过一个模板类,在封装分配内存和执行构造、析构函数两步,达到完美内存释放的目的

         运行结果如下:

          

从结果上看,已经实现调用了析构函数,再删除内存,这两步,同时也可以只调用析构函数不删除对象内存。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值