C++에서 가끔 클래스를 복사하게 되는 일이 생긴다.

클래스를 복사하는 방법에는 크게 얕은 복사와 깊은 복사라고 부르는 두 가지의 방법이 있다.

MSDN에서는 전자를 멤버 단위 복사라고 부른다.

클래스 내부의 변수들을 하나하나 똑같이 복사한다고 해서 그렇게 부르는 듯 하다.


 디폴트 생성자와 마찬가지로 멤버 단위 복사 생성자도 따로 복사 생성자를 선언하지 않는 이상 자동으로 생성된다.

따라서 따로 작업을 해주지 않는 이상 컴파일러는 디폴트로 얕은 복사를 수행한다.


 클래스 내에서 포인터를 이용해 메모리 작업을 하지 않는 이상 얕은 복사가 큰 문제가 되는 일은 많지 않을 테지만 만약 클래스 내에서 메모리를 할당하고 소멸자에서 이를 해제하게 하는 등의 작업을 한다면 치명적인 문제가 된다.


다음과 같은 클래스가 있다고 가정해 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass {
private:
    char *name;
public:
    MyClass(char*);
    ~MyClass() { delete[] name; }
    void printName() { std::cout << name << std::endl;  }
};
 
MyClass::MyClass(char* newName) {
    name = new char[strlen(newName) + 1];
    strcpy(name, newName);
}
cs


 멤버 변수로 name이라는 char형 포인터를 갖고, 생성자에서 이를 입력받아 할당하며 소멸자에서 해제하는 간단한 클래스이다. 클래스 멤버 함수로 name을 출력해주는 printName을 가지고 있다. 생성자에서 할당하고 소멸자에서 해제하므로 메모리 관리에 아무런 문제가 없어 보이지만, 이를 복사하게 되면 얘기는 달라진다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void printMyClass(MyClass m)
{
    std::cout << "<printMyClass>" << std::endl;
    m.printName();
}
 
 
int _tmain(int argc, _TCHAR* argv[])
{
    MyClass myClass("John");
    std::cout << "<main>" << std::endl;
    myClass.printName();
 
    printMyClass(myClass);
    return 0;
}
cs


 MyClass를 인자로 받고 main에서 인스턴스를 만들어서 이를 인자로 넘겨주면 printMyClass에서는 매개변수로 MyClass를 하나 만들고, 거기에 main에서 생성한 인스턴스를 그대로 복사한다.


얕은 복사가 진행되면서 main에서 생성한 인스턴스 속의 변수도 똑같이 복사되며, 문자열의 주소를 담고 있는 name 변수의 값도 똑같이 복사된다. 즉, 포인터 주소값이 그대로 복사되므로 main의 인스턴스와 같은 주소를 가리키게 된다.


이제 printMyClass 함수가 끝까지 정상적으로 실행되고 함수가 끝나면서 문제가 발생한다.

함수 시작 시 생성된 인스턴스가 할당 해제되면서 소멸자를 호출하고, 메인의 인스턴스의 변수와 함께 가리키고 있는 name 공간도 자동으로 할당이 해제되어 버린다.


이후 main에서 return으로 빠져나가면서 main에서 생성한 인스턴스가 또 할당 해제되고, 소멸자를 한번 더 호출하면서 이미 할당 해제된 공간을 한 번 더 해제하려고 하므로 에러가 발생한다.


 이런 식으로 얕은 복사를 하게 되면 예상치 못한 곳에서 치명적인 에러가 발생할 수 있다.

이런 문제를 해결하기 위해서 보통 class 내부에 복사 생성자를 생성하여 깊은 복사를 진행한다.

깊은 복사를 위해 class에 다음과 같이 복사 생성자를 추가해 주었다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyClass {
private:
    char *name;
public:
    MyClass(char*);
    MyClass(MyClass&);
    ~MyClass() { delete[] name; }
    void printName() { std::cout << name << std::endl;  }
};
 
MyClass::MyClass(char* newName) {
    name = new char[strlen(newName) + 1];
    strcpy(name, newName);
}
 
MyClass::MyClass(MyClass& newClass) {
    name = new char[strlen(newClass.name) + 1];
    strcpy(name, newClass.name);
}
cs


 이제 인스턴스를 복사할 때는 복사 생성자의 소스를 이용하여 깊은 복사를 진행하므로 새로 생성된 인스턴스의 name에도 따로 공간이 할당되고, 값이 똑같이 복사될 것이다. 이 메모리가 해제되더라도 main의 인스턴스가 가리키는 주소와는 연관이 없으므로 아무런 문제가 발생하지 않는다.



블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요