我正在使用自定义分配和删除工作一些内存空间,这是使用我无法控制的类似malloc的接口(即分配n个字节或释放分配的ptr)。所以,不像delete[]
。
现在,我想构建一个T&#39的数组。我用auto space_ptr = magic_malloc(n*sizeof(T))
得到了空间。现在我想做一些像array-placement-new这样的内容来构建n个元素。我该怎么办? ...或者我应该从1循环到n并构建单个T'
注意:
alignof(T)
除以sizeof(T)
)。如果你想解决对齐问题,那就更好了,但为了简单起见,你可能会忽略它。答案 0 :(得分:5)
我会假设你的记忆与你的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);
}
您可以执行基于标记调度的系统,以便在它们之间进行选择,具体取决于是否从T
构建Args&...
。如果它抛出,->~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[]
和N
的确切工作方式取决于实现.I&# 39;我只是勾勒出一种可以模仿它们的方式,它可能与它们在你的系统上的工作方式不兼容。例如,即使->~T()
是微不足道的,一些ABI也可能总是存储{{1}},或者无数其他变种。)
正如OP所指出的那样,在打扰上述内容之前,您可能还需要检查琐碎的构造。
答案 1 :(得分:0)
实际上,您可以将分配逻辑以及匹配的解除分配逻辑“插入”到内置的新表达式中。可以使用自定义运算符new和运算符delete来完成。实际上,placement new表达式采用任意数量的placement参数。这些参数用于查找重载的运算符new,也可以查找重载的运算符delete(如果有)。新的表达式将调用该运算符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 %lu\n", size);
return static_cast<T*>(::operator new(size));
}
void deallocate(T* p, size_t)
{
printf("deallocate\n");
return ::operator delete(p);
}
};
struct fail_halfway {
static size_t counter;
size_t idx;
fail_halfway()
: idx(++counter)
{
printf("I am %lu\n", idx);
if (idx == 5)
throw 42;
}
~fail_halfway()
{
printf("%lu dying\n", 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