gpt4 book ai didi

c++ - 在一个 C++ 程序中有不同的新运算符 : How to? 坏主意?

转载 作者:IT王子 更新时间:2023-10-28 23:34:46 24 4
gpt4 key购买 nike

我的代码中有不同的内存分配器:一个用于 CUDA(托管或非托管),一个用于纯主机内存。我还可以想象一种情况,当您想使用不同的分配算法时——例如,一种用于大型、长生命周期的 block ,另一种用于短生命周期的小对象。

我想知道如何正确实现这样的系统。

新展示位置?

我当前的解决方案使用placement new,指针决定使用哪个内存和内存分配器。然后在删除/取消分配对象时必须小心。目前,它有效,但我认为这不是一个好的解决方案。

MyObj* cudaObj = new(allocateCudaMemoryField(sizeof(MyObj)) MyObj(arg1, arg2);
MyObj* hostObj = new(allocateHostMemoryField(sizeof(MyObj)) MyObj(arg1, arg2);

重载新的,但是如何?

我想寻找一个带有重载 new 运算符的解决方案。如下所示:

MyObj* cudaObj = CudaAllocator::new MyObj(arg1, arg2);
MyObj* hostObj = HostAllocator::new MyObj(arg1, arg2);
CudaAllocator::delete cudaObj;
HostAllocator::delete hostObj;

我想我可以通过拥有一个命名空间 CudaAllocatorHostAllocator 来实现这一点,每个命名空间都有一个重载的 newdelete.

两个问题:

  • 在代码中有不同的 new 重载是否合理?这是设计缺陷的标志吗?
  • 如果可以的话,如何实现最好?

最佳答案

有重载运算符new/delete的时间和地点,但通常仅在用尽更简单的措施时才首选。

放置 new 的主要缺点是它要求调用者“记住”对象是如何分配的,并在对象到达末尾时采取适当的行动来调用相应的解除分配的生命周期。此外,要求调用者调用放置 new 在语法上很麻烦(我认为这是您提到的“不是一个好的解决方案”。)

重载 new/delete 的主要缺点是它意味着对给定类型执行一次(正如@JSF 指出的那样)。这将对象与其分配/解除分配的方式紧密结合。

重载新/删除

假设这个设置:

#include <memory>
#include <iostream>

void* allocateCudaMemoryField(size_t size)
{
std::cout << "allocateCudaMemoryField" << std::endl;
return new char[size]; // simulated
}
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; // simulated
}
void deallocateHostMemoryField(void* ptr, size_t)
{
std::cout << "deallocateHostMemoryField" << std::endl;
delete ptr;
}

这是带有重载 new/deleteMyObj(您的问题):

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 Plus Plusy 解决方案

更好的解决方案可能是结合使用 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);
}

打印:

allocateCudaMemoryField
MyObj()
allocateHostMemoryField
MyObj()
~MyObj()
deallocateHostMemoryField
~MyObj()
deallocateCudaMemoryField

通用版

这会变得更好。使用相同的策略,我们可以处理任何类的分配/解除分配语义。

class Factory
{
public:
// Generic versions that don't care what kind object is being allocated
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

我不想完全启动模板,但显然该代码已经成熟,可以干掉(参数化分配器函数的实现)。

注意事项

当您的对象相对较大且分配/取消分配的频率不高时,这非常有效。如果每秒有数百万个对象来来去去,我不会使用它。

一些相同的策略有效,但您还想考虑类似的策略

  • 在处理阶段开始/结束时进行批量分配/解除分配
  • 维护空闲列表的对象池
  • 用于容器的 C++ 分配器对象,例如 vector
  • 等等

这真的取决于你的需要。

tl;博士

没有。在这种情况下不要重载 new/delete。构建一个分配器,委托(delegate)给您的通用内存分配器。

关于c++ - 在一个 C++ 程序中有不同的新运算符 : How to? 坏主意?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31316687/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com