我应该如何在自定义分配API中使用placement new?

ein*_*ica 6 c++ memory-management placement-new c++11

我正在使用自定义分配和删除工作一些内存空间,这是使用我无法控制的类似malloc的接口(即分配n个字节或释放已分配的ptr).所以,没什么delete[].

现在,我想构建一个T的数组.我得到了空间auto space_ptr = magic_malloc(n*sizeof(T)).现在我想做一些像array-placement-new这样的内容来构建n个元素.我该怎么办?...或者我应该从1循环到n并构造单个T?

注意:

  • 我在这里忽略对齐问题(或者更确切地说,假设是alignof(T)分歧sizeof(T)).如果你想解决对齐问题,那就更好了,但为了简单起见你可能会忽略它.
  • C++ 11代码欢迎(实际上是首选),但没有C++ 14/17.

Yak*_*ont 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);
}
Run Code Online (Sandbox Code Playgroud)

和非例外的安全版本:

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);
}
Run Code Online (Sandbox Code Playgroud)

你可以做一个基于标签调度的系统来根据是否TArgs&...投掷构造来选择它们.如果它抛出并且->~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所指出的那样,您可能还需要在烦恼之前检查一些简单的构造.