programing tip

객체 배열을 동적으로 할당

itbloger 2021. 1. 10. 16:56
반응형

객체 배열을 동적으로 할당


이것은 일종의 초보자 질문이지만 오랫동안 C ++를 해본 적이 없으므로 여기에 있습니다 ...

동적으로 할당 된 배열을 포함하는 클래스가 있습니다.

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;
    }
}

하지만 이제는 이러한 클래스의 동적 할당 배열을 만들고 싶습니다. 내 현재 코드는 다음과 같습니다.

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

그러나 이것은 끔찍하게 폭발합니다. A( A(3)호출 과 함께 ) 생성 된 새 객체 for루프 반복이 완료 될 때 파괴 되고 이는 myArray해당 A인스턴스 의 내부 delete []-ed 됨을 의미하기 때문 입니다.

그래서 내 구문이 몹시 잘못되었다고 생각합니까? 과잉처럼 보이는 몇 가지 수정 사항이 있다고 생각합니다.

  • 에 대한 복사 생성자 만들기 A.
  • 사용 vector<int>하고 vector<A>그래서 나는이 모든에 대해 걱정할 필요가 없습니다.
  • 대신 필요없이 arrayOfAs배열 될 A오브젝트가 배열 될 수있는 A*포인터.

나는 이것이 내부 동적 할당이있는 것들의 배열을 동적으로 할당하려고 할 때 실제로 작동하는 구문이있는 초보자 일 뿐이라고 생각합니다.

(또한 C ++를 한 지 오래 되었기 때문에 스타일 비평도 감사했습니다.)

향후 시청자를위한 업데이트 : 아래의 모든 답변이 정말 도움이됩니다. Martin 's는 예제 코드와 유용한 "rule of 4"때문에 받아 들여지지 만, 모두 읽어 보는 것이 좋습니다. 일부는 무엇이 잘못되었는지에 대한 훌륭하고 간결한 진술이며, 일부는 vectors가 좋은 방법 인 방법과 이유를 올바르게 지적합니다 .


컨테이너를 빌드하려면 표준 컨테이너 (예 : std :: vector) 중 하나를 사용하고 싶을 것입니다. 그러나 이것은 객체에 RAW 포인터가 포함되어있을 때 고려해야 할 사항의 완벽한 예입니다.

객체에 RAW 포인터가있는 경우 3의 규칙 (이제 C ++ 11에서는 5의 규칙)을 기억해야합니다.

  • 건설자
  • 폐물 소각로
  • 생성자 복사
  • 할당 연산자
  • 생성자 이동 (C ++ 11)
  • 이동 할당 (C ++ 11)

이는 정의되지 않은 경우 컴파일러가 이러한 메서드의 자체 버전을 생성하기 때문입니다 (아래 참조). 컴파일러 생성 버전은 RAW 포인터를 다룰 때 항상 유용하지는 않습니다.

복사 생성자는 정정하기가 어렵습니다 (강력한 예외 보장을 제공하려는 경우 간단하지 않습니다). 할당 연산자는 내부적으로 복사 및 교체 관용구를 사용할 수 있으므로 복사 생성자 측면에서 정의 할 수 있습니다.

정수 배열에 대한 포인터를 포함하는 클래스의 절대 최소값에 대한 자세한 내용은 아래를 참조하십시오.

정답을 얻는 것이 사소하지 않다는 것을 알면 정수 배열에 대한 포인터 대신 std :: vector 사용을 고려해야합니다. 벡터는 사용하기 쉽고 (확장) 예외와 관련된 모든 문제를 다룹니다. 다음 클래스를 아래 A의 정의와 비교하십시오.

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

문제 살펴보기 :

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.
}

컴파일러가 생성 한 할당 연산자는 거의 모든 상황에 적합하지만 RAW 포인터가 작동 중이면주의해야합니다. 귀하의 경우에는 얕은 복사 문제로 인해 문제가 발생합니다 . 동일한 메모리 조각에 대한 포인터를 포함하는 두 개의 객체로 끝났습니다. A (3)가 루프의 끝에서 범위를 벗어나면 포인터에서 delete []를 호출합니다. 따라서 (배열에있는) 다른 객체는 이제 시스템에 반환 된 메모리에 대한 포인터를 포함합니다.

컴파일러가 생성 한 복사 생성자 ; 해당 멤버 복사 생성자를 사용하여 각 멤버 변수를 복사합니다. 포인터의 경우 이것은 포인터 값이 소스 객체에서 대상 객체로 복사됨을 의미합니다 (따라서 얕은 복사).

컴파일러가 할당 연산자를 생성했습니다 . 해당 멤버 할당 연산자를 사용하여 각 멤버 변수를 복사합니다. 포인터의 경우 이것은 포인터 값이 소스 객체에서 대상 객체로 복사됨을 의미합니다 (따라서 얕은 복사).

따라서 포인터를 포함하는 클래스의 최소값 :

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;
         }   
 }

std :: vector : 같은 것을 사용하는 것이 좋습니다.

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

STL의 약간의 과잉 사용에는 아무런 문제가 없으며 자전거를 재발 명하는 대신 앱의 특정 기능을 구현하는 데 더 많은 시간을 할애 할 수 있습니다.


A 개체의 생성자는 다른 개체를 동적으로 할당하고 동적으로 할당 된 개체에 대한 포인터를 원시 포인터에 저장합니다.

For that scenario, you must define 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).

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<>.

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

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 - delete handles that case by doing nothing so you don't have to clutter you code with the checks.


  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).

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


You need an assignment operator so that:

arrayOfAs[i] = A(3);

works as it should.


Why not have a setSize method.

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.

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);
}

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

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).

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);
}

ReferenceURL : https://stackoverflow.com/questions/255612/dynamically-allocating-an-array-of-objects

반응형