我会假设您的内存已与您的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 有一个重要的 dtor,您将必须在块中嵌入您创建的 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 所指出的,您可能还想在处理上述问题之前检查琐碎的构造。