如何在可能使用放置 new 的同时创建数组

How to create an array while potentially using placement new

本文关键字:new 数组 创建      更新时间:2023-10-16

我一直在努力创建自定义分配器作为一种有趣的练习/练习,我在创建数组时遇到了两个潜在的问题。对于典型的分配调用,我将使用 mallocplacement new 。但是,当我去创建一个数组时,我对应该如何完成它感到困惑。这一次,我注意到在某些地方似乎placement new对于像这里这样的数组来说可能不安全。我在尝试将placement new用于数组时也遇到了自己的错误。我会得到错误

错误 C2679:二进制 '=' :未找到采用类型为"SomeClass *"的右侧操作数的运算符(或者没有可接受的转换)

我理解错误(我相信),但我更愿意通过我的数组构造方法解决错误。我有两个问题

1) 分配器如何在不使用new[]的情况下创建数组?是placement new吗?如果是这样,我上面发布的链接中提到的潜在危险呢?

2)如果我应该使用placement new并在数组中的每个元素上调用它,为什么会出现上面提到的错误?

#include <stdio.h>
#include <new>
class SomeClass{
public:
    SomeClass() {
        printf("Constructedn");
    }
    ~SomeClass() {
        printf("Destructedn");
    }
};
void* SomeAllocationFunction(size_t size) {
    return malloc(size);
}
template<typename Type>
Type* SomeArrayAllocationFunction(size_t count){
    Type* mem = (Type*)SomeAllocationFunction(sizeof(Type) * count);
    for(unsigned int i = 0; i < count; ++i)
    {
        mem[i] = new(mem + i) Type();
    }
    return mem; 
}
int main(void){
    SomeClass* t = SomeArrayAllocationFunction<SomeClass>(2);
}

1) 分配器如何在不使用 new[] 的情况下创建数组?是新放置吗?如果是这样,我上面发布的链接中提到的潜在危险呢?

链接中的问题是误解了事物的运作方式。每个实现都有一个定义的实现方法来记录有关已分配数组的信息。单个对象不需要此信息,因为它由客户端通过实现delete进行管理。

对于数组,实现必须记录诸如元素计数、要调用的析构函数(如果适用)、元素大小......这些东西通常存储在返回的分配开始时,并且实现显然适当地抵消了分配请求的大小。因此,实际大小被偏移以容纳这些隐藏值。这就是为什么除非您的分配器进行额外的簿记(顺便说一句,std::allocator和收款接口带来的),否则malloc(sizeof...)将无法工作的原因。

要正确记录此信息,您可以定义static void* operator new[] 。要通过放置将您自己的分配器合并到此方案中,您可以使用以下方法:

// quick/dirty/incomplete illustration:
#include <stdio.h>
#include <new>
#include <cstdlib>
class t_allocator {
public:
    t_allocator() {
    }
    ~t_allocator() {
    }
public:
    void* allocate(const size_t& size) {
        return malloc(size);
    }
};
class SomeClass {
public:
    SomeClass() {
        printf("Constructedn");
    }
    ~SomeClass() {
        printf("Destructedn");
    }
public:
    static void* operator new[](size_t size, t_allocator& allocator) {
        return allocator.allocate(size);
    }
    /* in case static void* operator new[](size_t size, t_allocator& allocator) throws: */
    static void operator delete[](void* object, t_allocator& allocator) {
        /* ... */
    }
    static void operator delete[](void* object) {
        /* matches t_allocator::allocate */
        free(object);
    }
};
int main(void) {
    t_allocator allocator;
    SomeClass* t(new (allocator) SomeClass[2]);
    delete[] t;
    t = 0;
    return 0;
}

请注意,如果您的分配器可能会抛出,您将类似地实现放置operator delete[]

如果你想让你的分配器做一些簿记,它会变得混乱。 就个人而言,我认为该语言没有很好地实现这种情况,特别是因为数组初始化没有很好地实现。 总会有一个额外的步骤来执行接近构造或销毁,或者一些全局可访问的数据在此上下文中使用。

2)如果我想使用放置new并在数组中的每个元素上调用它,为什么会出现上面提到的错误?

如果要创建一个不经过operator new/operator new[]的分配器,则需要显式构造元素。 扩展上面的例子,你需要一个 destroy 方法,它调用 delete[] ,然后告诉this释放/重用内存(而不是使用上面的free)。

如果您只是想要一个快速的解决方案,则需要使用分配器或分配器来回移动析构函数、大小和元素计数。 在这种情况下,您不使用 new[]/delete[] .

编辑

如果你想自己管理书籍,这里有一种方法(可以有很多方向):

#include <cassert>
#include <stdio.h>
#include <new>
#include <cstdlib>
class t_allocator {
public:
  t_allocator() {
  }
  ~t_allocator() {
  }
public:
  /** tracks an array allocation's data. acts as a scope container for the allocation/types. */
  class t_array_record {
  public:
    typedef void (*t_destructor)(void* const);
    template<typename T>
    t_array_record(T*& outObjects, t_allocator& allocator, const size_t& count) : d_mem(allocator.allocate(sizeof(T), count)), d_destructor(t_allocator::t_array_record::Destruct<T>), d_size(sizeof(T)), d_count(count), d_allocator(allocator) {
      assert(this->d_mem);
      /* mind exceptions */
      char* const cptr(reinterpret_cast<char*>(this->d_mem));
      for (size_t idx(0); idx < this->d_count; ++idx) {
        /* assignment not required here: */
        new (&cptr[this->d_size * idx]) T();
      }
      outObjects = reinterpret_cast<T*>(this->d_mem);
    }
    ~t_array_record() {
      assert(this->d_mem);
      char* const cptr(reinterpret_cast<char*>(this->d_mem));
      for (size_t idx(0); idx < this->d_count; ++idx) {
        const size_t element(this->d_count - idx - 1U);
        this->d_destructor(& cptr[this->d_size * element]);
      }
      this->d_allocator.free(this->d_mem);
    }
  private:
    template<typename T>
    static void Destruct(void* const ptr) {
      T* const obj(reinterpret_cast<T*>(ptr));
      obj->~T();
    }
  private:
    void* const d_mem;
    t_destructor d_destructor;
    const size_t d_size;
    const size_t d_count;
    t_allocator& d_allocator;
  public:
    t_array_record(const t_array_record&);
    t_array_record& operator=(const t_array_record&);
  };
public:
  void* allocate(const size_t& size, const size_t& count) {
    return malloc(size * count);
  }
  void free(void* const mem) {
    ::free(mem);
  }
};

演示:

class SomeClass {
public:
  SomeClass() {
    printf("Constructedn");
  }
  virtual ~SomeClass() {
    printf("Destructedn");
  }
  virtual void greet() {
    printf("hi: %pn", this);
  }
private:
  SomeClass(const SomeClass&);
  SomeClass& operator=(const SomeClass&);
};
class SomeDer : public SomeClass {
  static int& N() {
    static int a(0);
    return ++a;
  }
public:
  SomeDer() : d_number(N()) {
    printf("Ctor-%in", this->d_number);
  }
  virtual ~SomeDer() {
    printf("~Der%i-", this->d_number);
  }
  virtual void greet() {
    printf("Der%i-", this->d_number);
    SomeClass::greet();
  }
private:
  const int d_number; /* << so we have different sized types in the example */
};
template<typename T>
void TryIt(const size_t& count) {
  t_allocator allocator;
  T* things(0);
  t_allocator::t_array_record record(things, allocator, count);
  for (size_t idx(0); idx < count; ++idx) {
    things[idx].greet();
  }
}
int main() {
  TryIt<SomeClass>(3);
  TryIt<SomeDer>(9);
  return 0;
}

mem[i]的类型为Type&,而new(mem + i) Type();的类型为Type*。这些显然是不兼容的类型,无法分配。我相信您可以完全删除作业,它会成功,仍然为您初始化该位置的内存。

不过,我仍然对实现自己的数组分配器持谨慎态度(例如,vector的自定义分配器会更明显)。