문제

이것은 일종의 초보자 질문이지만 오랫동안 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는 예제 코드와 유용한 "규칙 4"로 인해 받아 들여지지 만, 나는 그것들을 모두 읽는 것이 좋습니다. 어떤 사람들은 무엇이 잘못되었는지에 대한 간결하고 간결한 진술이며, 어떤 부분이 어떻게 그리고 이유를 정확하게 지적합니다. vectorS는가는 좋은 방법입니다.

도움이 되었습니까?

해결책

컨테이너를 건축하려면 표준 컨테이너 중 하나 (예 : STD :: 벡터)를 사용하려고합니다. 그러나 이것은 객체에 원시 포인터가 포함되어있을 때 고려해야 할 사항의 완벽한 예입니다.

객체에 원시 포인터가 있으면 3의 규칙을 기억해야합니다 (이제 C ++ 11의 규칙 5).

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

정의되지 않으면 컴파일러가 자체 버전의 이러한 방법을 생성하기 때문입니다 (아래 참조). 컴파일러 생성 버전이 원시 포인터를 처리 할 때 항상 유용하지는 않습니다.

카피 생성자는 정확하게 얻기 어려운 것입니다 (강력한 예외 보증을 제공하려면 사소하지 않습니다). 할당 연산자는 사본을 사용하고 관용구를 내부적으로 교환 할 수 있으므로 사본 생성자 측면에서 정의 할 수 있습니다.

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

올바르게 얻는 것은 사소한 일이라는 것을 알면 정수 배열에 대한 포인터보다는 std :: 벡터를 사용하는 것을 고려해야합니다. 벡터는 사용하기 쉽고 확장되며 예외와 관련된 모든 문제를 다룹니다. 다음 클래스를 아래의 정의와 비교하십시오.

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

컴파일러 생성 할당 연산자는 거의 모든 상황에서는 괜찮지 만, 원시 포인터가 플레이되면주의를 기울여야합니다. 귀하의 경우에는 문제가 발생합니다. 얕은 사본 문제. 당신은 같은 메모리 조각에 포인터를 포함하는 두 개의 객체로 끝났습니다. 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 :: 벡터를 사용하는 것이 좋습니다

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

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

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

그 시나리오에서 당신은 당신입니다 ~ 해야 하다 자신의 사본 생성자, 할당 연산자 및 소멸자를 정의하십시오. 컴파일러가 생성 된 컴파일러는 올바르게 작동하지 않습니다. (이것은 "Big Three의 법칙"에 대한 결론입니다. 어떤 소멸자, 과제 연산자, 카피 생성자가있는 클래스는 일반적으로 3 개 모두 필요합니다).

당신은 당신의 자신의 파괴자를 정의했지만 (그리고 당신은 카피 생성자 생성자 만들기를 언급했지만) Big Three의 다른 2 개를 정의해야합니다.

대안은 동적으로 할당 된 것에 대한 포인터를 저장하는 것입니다. int[] 당신을 위해 이러한 것들을 돌볼 다른 대상에서. A와 같은 것 vector<int> (당신이 언급했듯이) 또는 a boost::shared_array<>.

이것을 요약하려면 - Raii를 최대한 활용하려면 가능한 한 생 포인터를 다루지 않아야합니다.

그리고 다른 스타일의 비평을 요청했기 때문에 사소한 것은 원시 포인터를 삭제할 때 전화하기 전에 0을 확인할 필요가 없다는 것입니다. delete - delete 아무것도하지 않아서 해당 케이스를 처리하므로 수표로 코드를 혼란스럽게 할 필요가 없습니다.

  1. 객체에는 기본값 및 복사 생성자가있는 경우에만 배열 또는 공통 컨테이너를 사용하십시오.

  2. 포인터를 다른 방식으로 저장하십시오 (또는 스마트 포인터이지만이 경우 몇 가지 문제를 충족시킬 수 있음).

추신 : 항상 자체 기본값 및 복사 생성자를 정의하십시오. 그렇지 않으면 자동 생성이 사용됩니다.

할당 연산자가 필요합니다.

arrayOfAs[i] = A(3);

해야 할대로 작동합니다.

세트 크기 방법이없는 이유는 무엇입니까?

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

나는 "사본"을 좋아하지만이 경우 기본 생성자는 실제로 아무것도하지 않습니다. 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);
}

배치 기능을 사용합니다 new 연산자, 객체를 제자리에 생성하고 복사를 피할 수 있습니다.

배치 (3) : void* 연산자 New (std :: size_t size, void* ptr) noexcrect;

간단히 PTR을 반환합니다 (스토리지가 할당되지 않음). 그러나 함수가 새 발현에 의해 호출되면 적절한 초기화가 수행됩니다 (클래스 객체의 경우 기본 생성자 호출이 포함됩니다).

다음을 제안합니다.

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);
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top