我应该如何使用放置新的自定义分配API
How should I use placement new with a custom allocation API?
我正在处理一些具有自定义分配和删除的内存空间,这是使用类似malloc的接口制作的,这不在我的控制之下(即不透明的c风格函数用于"分配n个字节");或者"释放已分配的资源"。所以,不像new
或delete
。
现在,我想构造一个T
的数组,我用auto space_ptr = custom_alloc(n*sizeof(T))
来得到它的空间。现在我想做一些类似于array-placement-new的操作来原位构建n
元素。我该怎么做呢?... 或者我应该从1到n
循环并构建单个T
?
注意:
- 我在这里忽略对齐问题(或者更确切地说,假设
alignof(T)
除以sizeof(T)
)。如果你想解决对齐问题,那就更好了,但为了简单起见,你可以忽略它。 - 欢迎c++ 11代码(事实上是首选),但不支持c++ 14/17。
我将假设您的内存为您的T
充分对齐。你可能需要检查一下
下一个问题是异常。我们真的应该写两个版本,一个有可能导致构造异常,另一个没有。
我将写出异常安全的版本。
template<class T, class...Args>
T* construct_n_exception_safe( std::size_t n, void* here, Args&&...args ) {
auto ptr = [here](std::size_t i)->void*{
return static_cast<T*>(here)+i;
};
for( std::size_t i = 0; i < n; ++i ) {
try {
new(ptr(i)) T(args...);
} catch( ... ) {
try {
for (auto j = i; j > 0; --j) {
ptr(j-1)->~T();
}
} catch ( ... ) {
exit(-1);
}
throw;
}
}
return static_cast<T*>(here);
}
和非异常安全版本:
template<class T, class...Args>
T* construct_n_not_exception_safe( std::size_t n, void* here, Args&&...args ) {
auto ptr = [here](std::size_t i)->void*{
return static_cast<T*>(here)+i;
};
for(std::size_t i = 0; i < n; ++i) {
new (ptr(i)) T(args...);
}
return static_cast<T*>(here);
}
你可以做一个基于标签调度的系统来选择它们,这取决于从Args&...
构造T
是否抛出。如果它抛出,并且->~T()
是非平凡的,则使用异常安全的。
c++ 17公开了一些新函数来完成这些任务。他们可能会处理极端情况,我的就不会。
如果你试图模拟new[]
和delete[]
,如果T
有一个非平凡的医生,你必须嵌入多少T
你在块中创建。
sizeof(T)*N+K
,其中K
可能是sizeof(std::size_t)
。
现在在new[]
模拟器中,将N
塞进第一个位,然后在它后面的块上调用construct_n
。
在delete[]
中,从传入的指针中减去sizeof(std::size_t)
,读取N
,然后销毁对象(从右到左按照镜像构造顺序)。
这些都需要小心try
- catch
。
但是,如果~T()
是微不足道的,那么模拟的new[]
和delete[]
都不会存储额外的std::size_t
,也不会读取它。
(注意,这是如何模拟 new[]
和delete[]
。new[]
和delete[]
的具体工作方式取决于具体实现。我只是概述了一种您可以模仿它们的方法,它可能与它们在您的系统上的工作方式不兼容。例如,一些abi可能总是存储N
,即使->~T()
是微不足道的,或者无数的其他变体。
正如OP所指出的,在进行上述操作之前,您可能还需要检查是否存在琐碎的构造。
实际上,您可以将分配逻辑以及匹配的释放逻辑"插件"到内置的new表达式中。这可以通过自定义操作符new和操作符delete来实现。位置new表达式实际上接受任意数量的位置参数;这些实参用于查找重载操作符new和重载操作符delete(如果有的话)。New表达式将调用操作符New来分配内存并构造对象。如果数组的构造过程中途抛出异常,编译器将为您销毁这些已完成的对象,并在末尾调用匹配的delete操作符。
使用类似STL分配器接口的示例代码:
#include <cstdio>
#include <memory>
// tag type to select our overloads
struct use_allocator_t {
explicit use_allocator_t() = default;
};
// single-object forms skipped, just the same thing without []
template <class A>
void* operator new[](size_t size, use_allocator_t, A a)
{
using traits = std::allocator_traits<A>;
return traits::allocate(a, size);
}
template <class A>
void operator delete[](void* p, use_allocator_t, A a)
{
using traits = std::allocator_traits<A>;
return traits::deallocate(a, static_cast<typename traits::pointer>(p), 0);
}
template <class T>
struct barfing_allocator {
using value_type = T;
T* allocate(size_t size)
{
printf("allocate %lun", size);
return static_cast<T*>(::operator new(size));
}
void deallocate(T* p, size_t)
{
printf("deallocaten");
return ::operator delete(p);
}
};
struct fail_halfway {
static size_t counter;
size_t idx;
fail_halfway()
: idx(++counter)
{
printf("I am %lun", idx);
if (idx == 5)
throw 42;
}
~fail_halfway()
{
printf("%lu dyingn", idx);
}
};
size_t fail_halfway::counter = 0;
int main()
{
barfing_allocator<fail_halfway> a;
try {
new (use_allocator_t(), a) fail_halfway[10];
} catch(int) {
return 0;
}
return 1;
}
代码将打印:
allocate 88
I am 1
I am 2
I am 3
I am 4
I am 5
4 dying
3 dying
2 dying
1 dying
deallocate
- 自定义先决条件对移动分配运算符有效吗
- C++ - 没有自定义交换功能的移动分配运算符?
- 将 RTOS 队列对象封装在仅具有静态分配的 IQueue 自定义接口中
- 具有自定义构造函数 (C++) 的类型的动态数组分配
- 自己的自定义向量类. 内存重新分配
- shared_ptr的删除程序是否存储在自定义分配器分配的内存中?
- 动态分配自定义类的数组和重载运算符
- 为什么要对堆栈中的内存使用自定义动态内存分配?
- 如何使用boost ::与扩展STL容器的自定义容器分配
- 如何专用/分配自定义内存位置,以便可以在C 中编辑该位置
- 如何在 C++ 中安全地为 char *array 重新分配内存(它适用于自定义字符串类)
- 如何将sf::Font复制到自定义池分配的内存中
- 自定义内存分配和多个继承类的解除分配
- C++ 使用 brk() 系统调用分配内存的自定义内存管理
- 如何在SWIG中释放自定义构造函数中分配的内存
- 是否可以编写一个自定义 STL 分配器,该分配器使用指向用户提供的分配函数的指针
- 无法推送在队列中分配了"new"的自定义结构
- 是否可以使用自定义分配运算符来创建STACK对象
- 我应该如何使用放置新的自定义分配API
- 自定义分配运算符和迭代程序