在使用it技术时,有时候会用到重载运算符new
/delete
,但通常只有在其他简单措施已经无法解决问题的情况下才会使用。
放置new
的主要缺点是需要调用者“记住”对象的分配方式,并在该对象达到生命周期终点时采取适当的操作来调用相应的释放操作。此外,要求调用者调用放置new
语法上也是负担(我想这就是您提到的“不好的解决方案”)。
重载new
/delete
的主要缺点是它只能针对给定类型进行一次设置(正如@JSF所指出的)。这将一个对象与其分配/释放方式紧密耦合。
重载new/delete
假设有以下设置:
#include <memory>
#include <iostream>
void* allocateCudaMemoryField(size_t size)
{
std::cout << "allocateCudaMemoryField" << std::endl;
return new char[size];
}
void* allocateHostMemoryField(size_t size)
{
std::cout << "allocateHostMemoryField" << std::endl;
return new char[size];
}
void deallocateCudaMemoryField(void* ptr, size_t)
{
std::cout << "deallocateCudaMemoryField" << std::endl;
delete ptr;
}
void deallocateHostMemoryField(void* ptr, size_t)
{
std::cout << "deallocateHostMemoryField" << std::endl;
delete ptr;
}
这是带有重载new
/delete
的MyObj
(你的问题):
struct MyObj
{
MyObj(int arg1, int arg2)
{
cout << "MyObj()" << endl;
}
~MyObj()
{
cout << "~MyObj()" << endl;
}
static void* operator new(size_t)
{
cout << "MyObj::new" << endl;
return ::operator new(sizeof(MyObj));
}
static void operator delete(void* ptr)
{
cout << "MyObj::delete" << endl;
::operator delete(ptr);
}
};
MyObj* const ptr = new MyObj(1, 2);
delete ptr;
打印以下内容:
MyObj::new
MyObj()
~MyObj()
MyObj::delete
C++解决方案
更好的解决方案可能是使用RAII指针类型以及工厂来隐藏调用者的分配和释放细节。该解决方案使用放置new,但通过将删除器回调方法附加到unique_ptr来处理释放。
class MyObjFactory
{
public:
static auto MakeCudaObj(int arg1, int arg2)
{
constexpr const size_t size = sizeof(MyObj);
MyObj* const ptr = new (allocateCudaMemoryField(size)) MyObj(arg1, arg2);
return std::unique_ptr <MyObj, decltype(&deallocateCudaObj)> (ptr, deallocateCudaObj);
}
static auto MakeHostObj(int arg1, int arg2)
{
constexpr const size_t size = sizeof(MyObj);
MyObj* const ptr = new (allocateHostMemoryField(size)) MyObj(arg1, arg2);
return std::unique_ptr <MyObj, decltype(&deallocateHostObj)> (ptr, deallocateHostObj);
}
private:
static void deallocateCudaObj(MyObj* ptr) noexcept
{
ptr->~MyObj();
deallocateCudaMemoryField(ptr, sizeof(MyObj));
}
static void deallocateHostObj(MyObj* ptr) noexcept
{
ptr->~MyObj();
deallocateHostMemoryField(ptr, sizeof(MyObj));
}
};
{
auto objCuda = MyObjFactory::MakeCudaObj(1, 2);
auto objHost = MyObjFactory::MakeHostObj(1, 2);
}
输出:
分配Cuda内存字段
MyObj()
分配主机内存字段
MyObj()
~MyObj()
释放主机内存字段
~MyObj()
释放Cuda内存字段
通用版本
这变得更好了。使用相同的策略,我们可以处理任何类的分配/释放语义。
class Factory
{
public:
template <class T, class... Args>
static auto MakeCuda(Args... args)
{
constexpr const size_t size = sizeof(T);
T* const ptr = new (allocateCudaMemoryField(size)) T(args...);
using Deleter = void(*)(T*);
using Ptr = std::unique_ptr <T, Deleter>;
return Ptr(ptr, deallocateCuda <T>);
}
template <class T, class... Args>
static auto MakeHost(Args... args)
{
constexpr const size_t size = sizeof(T);
T* const ptr = new (allocateHostMemoryField(size)) T(args...);
using Deleter = void(*)(T*);
using Ptr = std::unique_ptr <T, Deleter>;
return Ptr(ptr, deallocateHost <T>);
}
private:
template <class T>
static void deallocateCuda(T* ptr) noexcept
{
ptr->~T();
deallocateCudaMemoryField(ptr, sizeof(T));
}
template <class T>
static void deallocateHost(T* ptr) noexcept
{
ptr->~T();
deallocateHostMemoryField(ptr, sizeof(T));
}
};
与新的S类一起使用:
struct S
{
S(int x, int y, int z) : x(x), y(y), z(z)
{
cout << "S()" << endl;
}
~S()
{
cout << "~S()" << endl;
}
int x, y, z;
};
{
auto objCuda = Factory::MakeCuda <S>(1, 2, 3);
auto objHost = Factory::MakeHost <S>(1, 2, 3);
}
打印:
allocateCudaMemoryField
S()
allocateHostMemoryField
S()
~S()
deallocateHostMemoryField
~S()
deallocateCudaMemoryField
我不想完全使用模板,但很明显那段代码需要DRY(在分配器函数上对实现进行参数化)。
考虑因素
当您的对象相对较大且不频繁分配/释放时,这种方法非常有效。如果您每秒有数百万个对象进出,则不建议使用此方法。
一些相同的策略可以使用,但您还需要考虑诸如以下策略:
- 在处理阶段开始/结束时进行批量分配/释放
- 维护空闲列表的对象池
- C++容器的分配器对象,例如
vector
- 等等
它真的取决于您的需求。
tl;dr
不,在这种情况下不要重载new
/delete
。构建一个委托给通用内存分配器的分配器。