C++ 动态分配对象数组

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/255612/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:04:54  来源:igfitidea点击:

Dynamically allocating an array of objects

c++memory-managementpointersdestructorcopy-constructor

提问by Domenic

I have a class that contains a dynamically allocated array, say

我有一个包含动态分配数组的类,比如说

class A
{
    int* myArray;
    A()
    {
        myArray = 0;
    }
    A(int size)
    {
        myArray = new int[size];
    }
    ~A()
    {
        // Note that as per MikeB's helpful style critique, no need to check against 0.
        delete [] myArray;
    }
}

But now I want to create a dynamically allocated array of these classes. Here's my current code:

但现在我想创建这些类的动态分配数组。这是我当前的代码:

A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
    arrayOfAs[i] = A(3);
}

But this blows up terribly. Because the new Aobject created (with the A(3)call) gets destructed when the forloop iteration finishes, and this means that the internal myArrayof that Ainstance gets delete []-ed.

但这会炸得很厉害。因为在循环迭代完成时A(通过A(3)调用)创建的新对象被破坏for,这意味着myArrayA实例的内部被delete []-ed。

So I think my syntax must be terribly wrong? I guess there are a few fixes that seem like overkill, which I'm hoping to avoid:

所以我认为我的语法一定是非常错误的?我想有一些修复似乎有点矫枉过正,我希望避免:

  • Creating a copy constructor for A.
  • Using vector<int>and vector<A>so I don't have to worry about all this.
  • Instead of having arrayOfAsbe an array of Aobjects, have it be an array of A*pointers.
  • A.
  • 使用vector<int>andvector<A>所以我不必担心这一切。
  • 它不是arrayOfAs一个A对象数组,而是一个A*指针数组。

I would think this is just some beginners thing where there's a syntax that actually works when attempting to dynamically allocate an array of things that have internal dynamic allocation.

我认为这只是一些初学者的事情,在尝试动态分配具有内部动态分配的数组时,有一种语法实际上有效。

(Also, style critiques appreciated, since it's been a while since I did C++.)

(此外,风格批评也很受欢迎,因为我已经有一段时间没有使用 C++ 了。)

Update for future viewers: All of the answers below are really helpful. Martin's is accepted because of the example code and the useful "rule of 4," but I really suggest reading them all. Some are good, succinct statements of what's wrong, and some point out correctly how and why vectors are a good way to go.

为未来的观众更新:下面的所有答案都非常有帮助。由于示例代码和有用的“4 法则”,Martin 被接受,但我真的建议阅读它们。有些是对错误的好的、简洁的陈述,有些则正确地指出了如何以及为什么vectors 是一个好的方法。

回答by Martin York

For building containers you obviously want to use one of the standard containers (such as a std::vector). But this is a perfect example of the things you need to consider when your object contains RAW pointers.

对于构建容器,您显然希望使用标准容器之一(例如 std::vector)。但这是当您的对象包含 RAW 指针时需要考虑的事情的一个完美示例。

If your object has a RAW pointer then you need to remember the rule of 3 (now the rule of 5 in C++11).

如果您的对象有一个 RAW 指针,那么您需要记住 3 规则(现在是 C++11 中的 5 规则)。

  • Constructor
  • Destructor
  • Copy Constructor
  • Assignment Operator
  • Move Constructor (C++11)
  • Move Assignment (C++11)
  • 构造函数
  • 析构函数
  • 复制构造函数
  • 赋值运算符
  • 移动构造函数 (C++11)
  • 移动赋值 (C++11)

This is because if not defined the compiler will generate its own version of these methods (see below). The compiler generated versions are not always useful when dealing with RAW pointers.

这是因为如果未定义,编译器将生成这些方法的自己版本(见下文)。在处理 RAW 指针时,编译器生成的版本并不总是有用。

The copy constructor is the hard one to get correct (it's non trivial if you want to provide the strong exception guarantee). The Assignment operator can be defined in terms of the Copy Constructor as you can use the copy and swap idiom internally.

复制构造函数是很难得到正确的(如果你想提供强大的异常保证,这很重要)。可以根据复制构造函数定义赋值运算符,因为您可以在内部使用复制和交换习语。

See below for full details on the absolute minimum for a class containing a pointer to an array of integers.

有关包含指向整数数组的指针的类的绝对最小值的完整详细信息,请参见下文。

Knowing that it is non trivial to get it correct you should consider using std::vector rather than a pointer to an array of integers. The vector is easy to use (and expand) and covers all the problems associated with exceptions. Compare the following class with the definition of A below.

知道让它正确是很重要的,你应该考虑使用 std::vector 而不是指向整数数组的指针。该向量易于使用(和扩展)并且涵盖了与异常相关的所有问题。将下面的类与下面 A 的定义进行比较。

class A
{ 
    std::vector<int>   mArray;
    public:
        A(){}
        A(size_t s) :mArray(s)  {}
};

Looking at your problem:

看你的问题:

A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
    // As you surmised the problem is on this line.
    arrayOfAs[i] = A(3);

    // What is happening:
    // 1) A(3) Build your A object (fine)
    // 2) A::operator=(A const&) is called to assign the value
    //    onto the result of the array access. Because you did
    //    not define this operator the compiler generated one is
    //    used.
}

The compiler generated assignment operator is fine for nearly all situations, but when RAW pointers are in play you need to pay attention. In your case it is causing a problem because of the shallow copyproblem. You have ended up with two objects that contain pointers to the same piece of memory. When the A(3) goes out of scope at the end of the loop it calls delete [] on its pointer. Thus the other object (in the array) now contains a pointer to memory that has been returned to the system.

编译器生成的赋值运算符几乎适用于所有情况,但是当 RAW 指针起作用时,您需要注意。在您的情况下,由于浅拷贝问题,它会导致问题。您最终得到了两个包含指向同一块内存的指针的对象。当 A(3) 在循环结束时超出范围时,它会在其指针上调用 delete []。因此,另一个对象(在数组中)现在包含一个指向已返回给系统的内存的指针。

The compiler generated copy constructor; copies each member variable by using that members copy constructor. For pointers this just means the pointer value is copied from the source object to the destination object (hence shallow copy).

编译器生成复制构造函数;使用成员复制构造函数复制每个成员变量。对于指针,这仅意味着将指针值从源对象复制到目标对象(因此是浅拷贝)。

The compiler generated assignment operator; copies each member variable by using that members assignment operator. For pointers this just means the pointer value is copied from the source object to the destination object (hence shallow copy).

编译器生成赋值运算符;使用成员赋值运算符复制每个成员变量。对于指针,这仅意味着将指针值从源对象复制到目标对象(因此是浅拷贝)。

So the minimum for a class that contains a pointer:

因此,包含指针的类的最小值:

class A
{
    size_t     mSize;
    int*       mArray;
    public:
         // Simple constructor/destructor are obvious.
         A(size_t s = 0) {mSize=s;mArray = new int[mSize];}
        ~A()             {delete [] mArray;}

         // Copy constructor needs more work
         A(A const& copy)
         {
             mSize  = copy.mSize;
             mArray = new int[copy.mSize];

             // Don't need to worry about copying integers.
             // But if the object has a copy constructor then
             // it would also need to worry about throws from the copy constructor.
             std::copy(&copy.mArray[0],&copy.mArray[c.mSize],mArray);

         }

         // Define assignment operator in terms of the copy constructor
         // Modified: There is a slight twist to the copy swap idiom, that you can
         //           Remove the manual copy made by passing the rhs by value thus
         //           providing an implicit copy generated by the compiler.
         A& operator=(A rhs) // Pass by value (thus generating a copy)
         {
             rhs.swap(*this); // Now swap data with the copy.
                              // The rhs parameter will delete the array when it
                              // goes out of scope at the end of the function
             return *this;
         }
         void swap(A& s) noexcept
         {
             using std::swap;
             swap(this.mArray,s.mArray);
             swap(this.mSize ,s.mSize);
         }

         // C++11
         A(A&& src) noexcept
             : mSize(0)
             , mArray(NULL)
         {
             src.swap(*this);
         }
         A& operator=(A&& src) noexcept
         {
             src.swap(*this);     // You are moving the state of the src object
                                  // into this one. The state of the src object
                                  // after the move must be valid but indeterminate.
                                  //
                                  // The easiest way to do this is to swap the states
                                  // of the two objects.
                                  //
                                  // Note: Doing any operation on src after a move 
                                  // is risky (apart from destroy) until you put it 
                                  // into a specific state. Your object should have
                                  // appropriate methods for this.
                                  // 
                                  // Example: Assignment (operator = should work).
                                  //          std::vector() has clear() which sets
                                  //          a specific state without needing to
                                  //          know the current state.
             return *this;
         }   
 }

回答by IMil

I'd recommend using std::vector: something like

我建议使用 std::vector: 类似的东西

typedef std::vector<int> A;
typedef std::vector<A> AS;

There's nothing wrong with the slight overkill of STL, and you'll be able to spend more time implementing the specific features of your app instead of reinventing the bicycle.

STL 的轻微矫枉过正并没有错,您将能够花更多时间实现应用程序的特定功能,而不是重新发明自行车。

回答by Michael Burr

The constructor of your A object allocates another object dynamically and stores a pointer to that dynamically allocated object in a raw pointer.

A 对象的构造函数动态分配另一个对象,并将指向该动态分配对象的指针存储在原始指针中。

For that scenario, you mustdefine your own copy constructor , assignment operator and destructor. The compiler generated ones will not work correctly. (This is a corollary to the "Law of the Big Three": A class with any of destructor, assignment operator, copy constructor generally needs all 3).

对于这种情况,您必须定义自己的复制构造函数、赋值运算符和析构函数。编译器生成的将无法正常工作。(这是“三巨头定律”的推论:具有任何析构函数、赋值运算符、复制构造函数的类通常需要全部 3 个)。

You have defined your own destructor (and you mentioned creating a copy constructor), but you need to define both of the other 2 of the big three.

您已经定义了自己的析构函数(并且您提到了创建复制构造函数),但是您需要定义三巨头中的另外两个。

An alternative is to store the pointer to your dynamically allocated int[]in some other object that will take care of these things for you. Something like a vector<int>(as you mentioned) or a boost::shared_array<>.

另一种方法是将指向您动态分配的指针存储int[]在其他一些将为您处理这些事情的对象中。类似于vector<int>(如您所提到的)或boost::shared_array<>.

To boil this down - to take advantage of RAII to the full extent, you should avoid dealing with raw pointers to the extent possible.

归根结底 - 要充分利用 RAII,您应该尽可能避免处理原始指针。

And since you asked for other style critiques, a minor one is that when you are deleting raw pointers you do not need to check for 0 before calling delete- deletehandles that case by doing nothing so you don't have to clutter you code with the checks.

并且由于您要求对其他样式进行批评,因此一个次要的问题是,当您删除原始指针时,您无需在调用之前检查 0 delete-无需delete执行任何操作即可处理这种情况,这样您就不必用检查来弄乱您的代码.

回答by Michael Burr

  1. Use array or common container for objects only if they have default and copy constructors.

  2. Store pointers otherwise (or smart pointers, but may meet some issues in this case).

  1. 仅当对象具有默认构造函数和复制构造函数时,才为对象使用数组或公共容器。

  2. 否则存储指针(或智能指针,但在这种情况下可能会遇到一些问题)。

PS: Always define own default and copy constructors otherwise auto-generated will be used

PS:始终定义自己的默认和复制构造函数,否则将使用自动生成的

回答by Jim Buck

You need an assignment operator so that:

您需要一个赋值运算符,以便:

arrayOfAs[i] = A(3);

works as it should.

正常工作。

回答by baash05

Why not have a setSize method.

为什么没有 setSize 方法。

A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
    arrayOfAs[i].SetSize(3);
}

I like the "copy" but in this case the default constructor isn't really doing anything. The SetSize could copy the data out of the original m_array (if it exists).. You'd have to store the size of the array within the class to do that.
OR
The SetSize could delete the original m_array.

我喜欢“复制”,但在这种情况下,默认构造函数并没有真正做任何事情。SetSize 可以将数据从原始 m_array 中复制出来(如果存在)。您必须在类中存储数组的大小才能做到这一点。

SetSize 可以删除原始 m_array。

void SetSize(unsigned int p_newSize)
{
    //I don't care if it's null because delete is smart enough to deal with that.
    delete myArray;
    myArray = new int[p_newSize];
    ASSERT(myArray);
}

回答by Saman Barghi

Using the placement feature of newoperator, you can create the object in place and avoid copying:

使用new运算符的放置功能,您可以就地创建对象并避免复制:

placement (3) :void* operator new (std::size_t size, void* ptr) noexcept;

Simply returns ptr (no storage is allocated). Notice though that, if the function is called by a new-expression, the proper initialization will be performed (for class objects, this includes calling its default constructor).

放置 (3) :void* operator new (std::size_t size, void* ptr) noexcept;

简单地返回 ptr (没有分配存储空间)。但是请注意,如果函数由 new 表达式调用,则将执行正确的初始化(对于类对象,这包括调用其默认构造函数)。

I suggest the following:

我建议如下:

A* arrayOfAs = new A[5]; //Allocate a block of memory for 5 objects
for (int i = 0; i < 5; ++i)
{
    //Do not allocate memory,
    //initialize an object in memory address provided by the pointer
    new (&arrayOfAs[i]) A(3);
}