【问题标题】:Simulating new[] with argument constructor使用参数构造函数模拟 new[]
【发布时间】:2011-05-20 09:54:29
【问题描述】:

如果我没有修改参数构造函数中的任何static 变量,是否低于模拟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)
}

【问题讨论】:

  • 你可以通过传入const T&amp; value而不是T value来避免一些复制
  • T value ... memcpy(..., &amp;value, ...); 肯定不是一个好主意。
  • 我认为这种方法完全错误。 new 的任何变体不是应该构造任何东西。它只返回要调用的构造函数的地址。此外,您还需要提供匹配的operator delete,以防构造失败。但是不,不要这样做。使用std::vector
  • 如果这行得通,为什么还没有使用该语言?
  • @Alextandre,感谢您指出。我错过了在默认构造函数中进行调试:)。

标签: c++ arrays constructor new-operator


【解决方案1】:

我建议

 std::vector<A> v(10, A(1,2));

我意识到这并不能真正解决数组的问题。 你可以使用

 p = &v[0]; 

因为标准保证连续存储。调整矢量大小时要非常小心,因为它可能会使 p 无效

我检查了 boost::array(它适应 C 风格的数组),但它没有定义构造函数...

【讨论】:

    【解决方案2】:

    不是好的。您正在将对象复制到未初始化的内存中,而没有调用正确的复制语义。

    只要您只使用 POD,就可以了。但是,在使用不是 POD 的对象时(例如您的 A),您需要采取预防措施。

    除此之外,operator new 不能以这种方式使用。正如Alexandre 在 cmets 中指出的那样,数组不会被正确初始化,因为 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。这是对memcpy巨大改进。
    • 不,不要在operator new 中构造任何东西。它应该只返回一个地址,句号。
    • @AlexandreC。不在这个超载中。问题是在构造对象之后明确地完全没问题。您正在考虑单参数::operator new,这是完全不同的事情。
    • @Konrad:placement new 和 nothrow new 都不会在它们的定义中构造任何东西。它们只返回构造对象的地址。如果你这样做(我怀疑这是可能的,因为你的对象将被默认构造的对象覆盖,可能会泄漏很多东西),那么你还必须提供匹配的operator delete,以防你的operator new 抛出一个例外。
    • @AlexandreC。是的,但它们是不同的。这个重载是一个完全不同的函数,只是碰巧有相同的名字。这构造了对象。 C++ 没有限制说重载的operator new 不能构造对象,实际上恰恰相反——C++ 已经定义了构造对象的重载:例如考虑new int[10] ()。这将创建一个包含 10 个 T 类型对象的 C 数组并初始化它们
    【解决方案3】:

    这不行 - 如果typename T 有这样的对象(在您的示例中struct A 确实有一个),C++ 将调用这些对象非平凡的默认构造函数,这将导致在已占用的内存中重建对象。

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

    【讨论】:

      【解决方案4】:

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

      可能需要这个额外的内存,因为 C++ 必须知道在delete[] p; 的情况下要销毁多少对象,但它不能可靠地使用new p[sz] 分配的内存块的大小来推断这个数字,因为内存可能已经询问自定义内存管理器,因此(例如您的情况)仅通过知道指针无法知道分配了多少内存。

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

      【讨论】:

        【解决方案5】:
        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 无论是出于习惯还是出于必要,有些人在内存分配方面都是控制狂。我在这里使用了 malloc,但它可以是任何其他内存分配器。
        • @Alexandre 这些函数提供了类似 new[] 的语义,同时提供了对 OP 所要求的对象构造的控制。并非所有数组问题都是 XY 问题,需要用 STL 来回答。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-04-28
        • 1970-01-01
        • 1970-01-01
        • 2011-02-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多