使用带参数的构造函数来模拟new[]

5
如果我在参数构造函数中没有修改任何static变量,那么下面的代码是模拟带参数的数组new操作new T[N] (x,y);的正确方式吗?
template<typename T>
void* operator new [] (size_t size, const T &value)
{
  T* p = (T*) malloc(size);
  for(int i = size / sizeof(T) - 1; i >= 0; i--)
    memcpy(p + i, &value, sizeof(T));
  return p;
}

使用方法如下:

struct A
{
  A () {}  // default
  A (int i, int j) {} // with arguments
};

int main ()
{
  A *p = new(A(1,2)) A[10];  // instead of new A[10](1,2)
}

1
你可以通过传递 const T& value 而不是 T value 来避免一些复制。 - sehe
2
T value ... memcpy(..., &value, ...); 绝对不是一个好主意。 - Martin Ba
1
我认为这种方法是完全错误的。任何形式的new都不应该构造任何东西。它只返回一个地址,以便调用构造函数。此外,如果构造失败,您还需要提供匹配的operator delete。但是不要这样做。使用std::vector - Alexandre C.
如果这个功能是有效的,为什么它不早就被纳入语言中了呢? - Bo Persson
@Alextandre,感谢您指出这一点。我忘记在默认构造函数中放置调试 :). - iammilind
5个回答

5
我建议
 std::vector<A> v(10, A(1,2));

我知道这并没有真正回答关于数组的问题。你可以使用


 p = &v[0]; 

由于标准保证连续存储,因此请注意调整vector的大小,因为它可能会使p失效。

我检查了boost :: array <>(适配C样式数组),但它没有定义构造函数...


4

这样做是不好的,你正在将对象复制到未初始化的内存中,没有调用适当的复制语义。

只要您只使用POD,这很好。然而,在处理不是POD的对象(例如您的A)时,您需要采取预防措施。

除此之外,operator new不能以这种方式使用。正如Alexandre在评论中指出的那样,由于C++在调用您的operator new后将为所有元素调用构造函数,因此覆盖值,因此数组将无法正确初始化:

#include <cstdlib>
#include <iostream>

template<typename T>
void* operator new [] (size_t size, T value) {
    T* p = (T*) std::malloc(size);
    for(int i = size / sizeof(T) - 1; i >= 0; i--)
        new(p + i) T(value);
    return p;
}

struct A {
    int x;
    A(int x) : x(x) { std::cout << "int ctor\n"; }
    A() : x(0) { std::cout << "default ctor\n"; }
    A(const A& other) : x(other.x) { std::cout << "copy ctor\n"; }
};

int main() {
    A *p = new(A(42)) A[2];
    for (unsigned i = 0; i < 2; ++i)
        std::cout << p[i].x << std::endl;
}

这将得出:
int ctor
copy ctor
copy ctor
default ctor
default ctor
0
0

...不是期望的结果。


1
对于 placement-new,加1。这比 memcpy 要好得多。 - Nawaz
2
不要在 operator new 中构造任何东西,它应该只返回一个地址,仅此而已。 - Alexandre C.
@AlexandreC。不是在这个重载函数中。问题明确是关于构造对象之后的,这完全没问题。你可能想到了一个参数的::operator new,但那是完全不同的事情。 - Konrad Rudolph
1
@Konrad:无论是放置 new 还是 nothrow new,在它们的定义中都没有构造任何东西。它们只返回对象应该被构造的地址。如果您这样做(我怀疑这是可能的,因为您的对象将被默认构造的对象覆盖,可能会泄漏很多东西),那么您还必须提供匹配的 operator delete,以防您的 operator new 抛出异常。 - Alexandre C.
@AlexandreC。是的,但它们是不同的。这个重载是一个完全不同的函数,只是碰巧有相同的名称。这也构造对象。C++没有限制重载的 operator new 不能构造对象,事实上相反 - C++已经定义了构造对象的重载:例如考虑 new int [10]()。这将创建一个类型为 T 的10个对象的C数组,并初始化它们 - Konrad Rudolph
显示剩余2条评论

2

这样做是不可行的 - 如果typename T有非平凡的默认构造函数(例如您示例中的struct A),则C ++将调用这些对象的非平凡默认构造函数,这将导致在已经占用内存的情况下重建对象。

一个适当的解决方案是使用std::vector(推荐)或调用::operator new[]来分配内存,然后使用placement-new调用构造函数,并注意任何异常。


1

你应该考虑到operator new[]可能会请求比sizeof(T) * n更多的内存。

这些额外的内存可能是必要的,因为在delete[] p;的情况下,C++必须知道要销毁多少个对象,但它不能可靠地使用由new p[sz]分配的内存块的大小来推断这个数字,因为内存可能已经被自定义内存管理器所请求,所以(例如您的情况)只知道指针并不能知道分配了多少内存。

这也意味着你提供已初始化对象的尝试将失败,因为实际上返回给应用程序的数组可能不会从你自定义的operator new[]返回的地址开始,因此初始化可能会错位。


0
template <typename myType> myType * buildArray(size_t numElements,const myType & startValue) {
  myType * newArray=(myType *)malloc(sizeof(myType)*numElements);

  if (NULL!=newArray) {
    size_t index;
    for (index=0;index<numElements;++index) {
      new (newArray+index) myType(startValue);
    }
  }

  return newArray;
}

template <typename myType> void destroyArray(size_t numElements,myType * oldArray) {
  size_t index;
  for (index=0;index<numElements;++index) {
    (oldArray+index)->~myType();
  }
  free(oldArray);
}

A * p=newArray(10,A(1,2));
destroyArray(10,p);

根据您正在构建的平台,destroyArray也可以像这样编写:

template <typename myType> void destroyArray(myType * oldArray) {
  size_t numElements=malloc_size(oldArray)/sizeof(myType); //or _msize with Visual Studio
  size_t index;
  for (index=0;index<numElements;++index) {
    (oldArray+index)->~myType();
  }
  free(oldArray);
}

请告诉我这段代码怎么比std::vector更有优势? - Alexandre C.
@Alexandre 有些人在内存分配方面是控制狂。我在这里使用了malloc,但也可以使用其他任何内存分配器。 - IronMensan
@Alexandre 这些函数提供了类似于 new[] 的语义,同时提供了对对象构造的控制,这正是 OP 所询问的。并非所有的数组问题都是 XY 问题,需要用 STL 来回答。 - IronMensan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接