【问题标题】:C++, array of objects without <vector>C++,没有 <vector> 的对象数组
【发布时间】:2010-03-22 15:24:06
【问题描述】:

我想在 C++ 中创建一个不使用 STL 的对象数组。

我该怎么做?

如何创建没有无参数构造函数(默认构造函数)的 Object2 数组?

【问题讨论】:

  • 出于兴趣,为什么不想用stl?
  • @osgx:为什么不简单地定义一个默认构造函数?
  • @KennyTM 这不是一个好的解决方案。许多类不能也不应该是默认可构造的。
  • 这是一个很好的练习,您将处理显式内存管理,我很高兴您尝试最小化要求(恕我直言,没有默认构造函数是一个明智的约束)。完成后,一旦你见证了你遇到的所有错误,你会很高兴已经有可用的库(STL 和 Boost)。
  • +1 因为在每一次点击中,我都会检查“创建 c++ 对象数组”,每个人都说使用向量。 std::vector 并不总是合适的(尽管我同意在大多数情况下它绝对是可取的。)

标签: c++ arrays


【解决方案1】:

如果有问题的类型有无参数构造函数,请使用new[]

Object2* newArray = new Object2[numberOfObjects];

当您不再需要该数组时,不要忘记致电delete[]

delete[] newArray;

如果没有这样的构造函数,使用operator new分配内存,然后就地调用构造函数:

//do for each object
::new( addressOfObject ) Object2( parameters );

同样,不要忘记在不再需要数组时释放它。

【讨论】:

  • Object2 没有默认的 c'tor。
  • @Andrey:这是普通的旧位置新。
  • @osgx: void* buffer = ::operator new[]( numberOfObjects * sizeof( Object2 ));那不是对象数组,而是一块内存。
  • @Jeremy:有什么替代方案?对象没有默认ctor,他不想使用vector
  • 放置新绝对是正确的答案。 @sharptooth:它最初是一块原始内存,当调用构造函数时它变成一个对象数组。但请务必正确处理异常并手动调用析构函数,并且仅在那些完成构造的对象上调用。这不是微不足道的。
【解决方案2】:

假设你的类是 Base 并且你有一个单参数构造函数

Base arr[3] = {Base(0), Base(1), Base(2)} ;

【讨论】:

  • 如果您在编译时知道数组的大小,这是最好的解决方案,如果不是真的使用向量。
  • 我只知道集装箱建造时的尺寸。
【解决方案3】:
// allocate memory
Object2* objArray = static_cast<Object2*>( ::operator new ( sizeof Object2 * NUM_OF_OBJS ) );
// invoke constuctors
for ( size_t i = 0; i < NUM_OF_OBJS; i++ )
  new (&objArray[i]) Object2( /* initializers */ );

// ... do some work

// invoke destructors
for ( size_t i = 0; i < NUM_OF_OBJS; i++ )
  objArray[i].~Object2();

// deallocate memory
::operator delete ( objArray );

【讨论】:

  • static_cast 是这样工作的吗? (我一直认为你需要 reinterpret_cast 才能从 void* 到其他任何东西,但老实说从未尝试过 static_cast。)......并且你忘记在删除数组之前销毁你的对象,你需要显式调用 dtor: for (. ..) objArray[i].~Object2();
  • 为什么我们需要一个 static_cast 而不是简单的 (Object2*) 一个?
  • @osgx:使用 C++ 样式转换而不是 C 样式转换意味着您始终可以获得您要求的转换类型。如果你犯了一个错误,那么你会得到一个 copmile 错误,而不是退回到 C 风格转换会给你带来的潜在更危险的转换。
  • @dash-tom-bang,thanx,我完全忘记了析构函数 :) static_cast 在这里更安全,因为它不能将 Object2* 转换为 SomeThingElse* reinterpret_cast(或C-style cast) 可以。
  • 显式调用析构函数?你确定这不是……你知道……邪恶吗?
【解决方案4】:
Object2 *myArray[42];
for (int i = 0; i < 42; i++)
{
  myArray[i] = new Object2(param1, param2, ...);
}

稍后您将不得不遍历数组并单独释放每个成员:

for (int j = 0; j < 42; j++)
{
  delete myArray[j];
}

【讨论】:

  • Object2 myArray[42]; 除非Object2 具有默认构造函数(用户声明或其他方式),否则此步骤将不起作用。
  • @Charles Bailey。是的,我注意到在我发布它之后的一秒钟,并将其更改为一个指针数组。
  • 嘿嘿。不是对象数组,是指针数组!
【解决方案5】:

使用指向 Object2 的指针数组:

std::tr1::shared_ptr<Object2>* newArray = new shared_ptr<Object2>[numberOfObjects];
for(int i = 0; i < numberOfObjects; i++)
{
    newArray[i] = shared_ptr<Object2>(new Object2(params));
}

或者,或者,不使用 shared_ptr:

Object2** newArray = new Object2*[numberOfObjects];
for(int i = 0; i < numberOfObjects; i++)
{
    newArray[i] = new Object2(params);
}

【讨论】:

  • 正如其他人指出的那样,这会增加大量内存使用开销(使用一堆堆块,每个堆块都需要一个堆头)并破坏空间局部性。
  • @Ben Voigt,“一堆堆块”是什么意思?堆头有多长,每个new Object2(params)是否需要?
  • 堆头的大小随编译器设置以及调试堆是否正在使用而变化,但对于我熟悉的实现,它是 minimum 的 2* sizeof(void*),对于 32 位进程来说是 8 个字节。对 ::new、::new[] 或 malloc 的每次调用都必须在它返回的堆块内包含堆头空间,并且返回地址几乎可以保证是 sizeof(void*) 的倍数。 new char() 在调试版本中实际分配 64 个字节或更多字节并不少见,它检查下溢、溢出、记录行分配等。
  • @Ben Voigt,我明白了。谢谢!。接下来的事情呢?使用固定长度分配的池(例如array_of_16byte_objects),堆将增加objects_count_of_16byte的计数器,并将该位置标记为位图中已填充。你知道这样的实现吗?
  • 那里有备用分配器,它们按照您的建议使用池(但是当池用完时会发生什么,或者池中唯一的“空闲”内存并且需要分配一些其他大小的对象)。您实际上可以为您的班级重新定义“新”以使用该池。但是,当您首先想要一个大阵列时,跟踪单个池插槽的使用情况是没有意义的。仅使用一个大缓冲区以及放置 new 比池更有效。现在,如果您需要一个链表或其他可以增长的东西,那么池可能会更好。但不适用于数组。
【解决方案6】:

您可以做std::vector 所做的事情并创建一块原始内存。然后,您可以使用placement new 构造在该内存中没有默认构造函数的对象,因为它们是必需的。但是,当然,如果您这样做,您不妨一开始就使用std::vector

【讨论】:

    【解决方案7】:

    如果没有可用的默认构造函数,则需要一个指针数组,然后遍历该数组以初始化每个指针。

    【讨论】:

    • 这不太可能很快:分配问题和缓存未命中很可能。
    【解决方案8】:

    显而易见的问题是您为什么不想使用 STL。

    假设您有一个理由,您将创建一个对象数组,其中包含Obj * op = new Obj[4]; 之类的内容。请记住使用delete [] op; 摆脱它。

    对于没有构造函数且不带参数的对象,您无法做到这一点。在这种情况下,我认为你能做的最好的就是分配一些内存并使用新的放置。它不像其他方法那么简单。

    【讨论】:

    • 我正在尝试从头开始实现类似 stl 的结构。它将不是一个向量,而是类似于恒长向量的 B 树。而且我想要完全控制,所以我想自己实现它。
    【解决方案9】:

    如果你真的需要一个非默认可构造类型的数组(对象的连续序列),并且由于某种原因你不能让用户std::vector(!?)那么你必须使用原始分配函数和放置新的。

    这很难可靠地做到;这应该有助于说明原因。这个 sn-p 包括一些对异常的防御,但很可能对所有故障都不是健壮的。

    const size_t required_count = 100; //e.g.
    
    // cast to pointer of required type needed for pointer arithmetic
    Object2* objarray = static_cast<Object2*>(operator new(required_count * sizeof(Object2)));
    
    size_t construction_count = 0;
    
    try
    {
        while (construction_count < required_count)
        {
            // params could change with index.
            new (static_cast<void*>(objarray + construction_count)) Object2(param1, param2);
            ++construction_count;
        }
    }
    catch (...)
    {
        while (construction_count-- != 0)
        {
            try
            {
                (&objarray[construction_count])->~Object2();
            }
            catch (...)
            {
                // not a lot we can do here, log but don't re-throw.
            }
        }
    
        operator delete(objarray);
        throw;
    }
    
    // Now objarray has been allocated and pointer to an array of required_count Object2
    // It cannot be de-allocated via delete[] or delete; you must loop through
    // calling destructors and then call operator delete on the buffer.
    

    【讨论】:

    • 避免 STL 的原因有很多,避免 STL 的人也倾向于避免异常。 ;)
    • 我可以使用objarray[i]-&gt;Object2(param1,param2) 来构建对象而不放置新对象吗?
    • @dash-tom-bang:我试图尽可能保持异常中立,仅防止内存分配失败和客户端对象引发的异常。我不会抛出其他解决方案没有的任何新异常;他们(通常)只是忽略了我没有的潜在例外情况。
    • @osgx:没有。放置new 是构造对象的唯一方法,而无需在同一语句中分配内存。您不能在现有对象上调用构造函数,这是您建议的语法所暗示的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-22
    • 1970-01-01
    • 2010-12-18
    • 1970-01-01
    • 2011-02-14
    • 1970-01-01
    相关资源
    最近更新 更多