Language/C++

[C++] 13.Unmanaged Programming: 복사 생성자

coco_daddy 2024. 1. 25. 13:39

복사 생성자

인스턴스의 사본을 만드는 것.

만약 들고 있는 것이 레퍼런스라면, 레퍼런스의 주소값을 복사해줄 것인지의 문제가 있다.

// Vector.h

class Vector
{
public:
    Vector(const Vector& other);
private:
    int mX;
    int mY;
};

// Vector.cpp

Vector::Vector(const Vector& other)
    : mX(other.mX)
    , mY(other.mY) // private 이지만, 같은 class 이기 때문에 접근 가능하다.
{
}

같은 클래스의 다른 개체를 이용하여 새로운 개체를 초기화하는 것은 다음과 같다.

Vector(const Vector& other);

Vector a;        // default constructor 또는 매개변수 없는 생성자 호출
Vector b(a);     // copy constructor

암시적 복사 생성자

암시적 복사 생성자는 코드에 복사 생성자가 없는 경우, 컴파일러가 암시적으로 생성해준다.

Vector() {}
Vector(const Vector& other)
    : mX(other.mX)
    , mY(other.mY)
{
}

이 경우 얕은 복사를 수행한다.

  • 멤버 별로 복사한다.
  • 각 멤버의 값을 복사한다.
  • 개체인 멤버 변수는 해당 개체의 복사 생성자가 호출된다.

포인터 변수의 복사를 보기 위한 예제

// ClassRecode.h

class ClassRecode
{
public:
    ClassRecord(const int *scores, int count);
    ~ClassRecord();
private:
    int mCount;
    int *mScores;
};

// ClassRecord.cpp

ClassRecord::ClassRecord(const int* scores, int count)
    : mCount(count)
{
    mScores = new int[mCount];
    memcpy(mScores, scores, mCount * sizeof(int));
}

ClassRecord::~ClassRecord()
{
    delete[] mScores;
}

포인터는 얕은 복사가 되기 때문에 주의가 필요하다.

// ClassRecord.cpp

ClassRecord::ClassRecord(const int* scores, int count)
    : mCount(count)
{
    mScores = new int[mCount];
    memcpy(mScores, scores, mCount * sizeof(int));
}

// 컴파일러가 만들어주는 암시적 복사 생성자
ClassRecord::ClassRecord(const ClassRecored& other)
    : mCount(other.mCount)
    , mScores(other.mScores)
{
}
// main.cpp
ClassRecord classRecord(scores, 5);
ClassRecord classRecordCopy = new ClassRecord(classRecord);
delete classRecordCopy;
  1. 첫 번째 오브젝트가 스택에 만들어진다.
    classRecord의 생성자가 만들어지면서 sizeof(int) * 5 가 힙영역에 할당된다.
  2. 두 번째 오브젝트가 classRecord의 값을 복사하면서 힙에 만들어진다.
    여기서 int*의 값은 classRecord가 가리키고 있는 힙영역의 int 배열의 위치와 같다.
  3. 힙에 할당된 오브젝트가 삭제되면서 소멸자가 호출된다.
    → classRecord가 가리키고 있는 힙영역의 int 배열을 할당 해제한다.

사용자가 만드는 복사 생성자

  • 클래스 안에서 동적할당을 한다면 얕은 복사가 일어날 가능성이 매우 높다.
  • 깊은 복사를 하는 복사 생성자를 만들어주는 것이 안전하다.
    • 포인터 변수가 가리키는 실제 데이터도 복사해주너야 한다.
ClassRecord::ClassRecord(const ClassRecord& other)
    : mCount(other.mCount)
{
    mScores = new int[mCount];
    memcpy(mScores, other.mScores, mCount * sizeof(int));
}