-
[객체의 복사와 대입 연산자] 의도하는 동작이 있다면 구현하라개발/C·C++ 2021. 4. 26. 12:02
primitive type이 자연스럽게 연산이 가능한 이유는 이미 구현되어 있기 때문입니다. = 연산자를 사용해 값을 초기화할수도 있고 복사도 할 수 있습니다. 우리는 그냥 사용하기만 하면 됩니다. 우리가 정의한 클래스도 초기화 하고 복사도 하며 대입도 됩니다. 복사와 대입 기능을 따로 구현한 적 없는데 되긴 됩니다. 친절은 여기까지입니다.
컴파일러가 자동으로 수행해주는 복사와 대입은 값 복사만 해줍니다. 만약 동적으로 메모리를 할당한 포인터 변수가 있다면 값 복사는 매우 치명적인 오류의 원인이 됩니다. 객체가 여러 개인데 그 객체의 포인터가 가리키는 메모리가 한 곳이라고 생각해보세요. 끔찍합니다. 어느 한 객체의 포인터 변수만 해제가 되어도 나머지 객체의 포인터 변수는 dangling pointer가 됩니다. 다음 코드를 살펴보세요.
#include <iostream> using namespace std; class Student { private: int _age; int _score; char* _name; public: Student(){} Student(int age, int score, const char* name) : _age(age), _score(score), _name(new char[strlen(name) + 1]) { strcpy(_name, name); } ~Student() { delete[] _name; } } int main() { Student s0(15, 77, "John"); Student s1 = s0; // 복사 Student s2; s2 = s0; // 대입 }
딱히 문제가 있어 보이지 않습니다. 적어도 컴파일 타임의 오류는 없습니다. 빨간줄이 안 떴으니까 한 번 실행을 해보겠습니다. 런타임 오류가 발생합니다. 처음에 언급했던 메모리 해제 문제 때문입니다. 값만 복사돼서 하나의 메모리를 두 개 이상의 변수가 가리키고 있기 때문입니다. 이를 얕은복사라고 합니다. 여기에선 char*를 쓰지 않고 string을 이용해서 이름을 받으면 간단하게 해결됩니다. 예제는 동적 메모리를 가리키는 포인터 멤버 변수가 있을 때 복사 생성자를 만들지 않고 대입 연산자를 오버로딩하지 않았을 때의 문제를 보여주기 위해 작성됐습니다.
복사는 복사 생성자에서 위임 생성자를 이용해 값을 초기화하면 편합니다. 대입 연산자에서는 기존의 _name의 메모리를 해제해준 다음에 제대로 메모리를 할당해줍니다.
#include <iostream> using namespace std; class Student { private: int _age; int _score; char* _name; public: Student(){} Student(int age, int score, const char* name) : _age(age), _score(score), _name(new char[strlen(name) + 1]) { strcpy(_name, name); } // 복사 생성자 Student(const Student& s) : Student(s._age, s._score, s._name) {} // 대입 연산자 오버로딩 Student& operator=(const Student& s) { _age = s._age; _score = s._score; if(_name) { delete _name; _name = nullptr; } _name = new char[strlen(s._name) + 1]; strcpy(_name, s._name); return *this; } ~Student() { delete[] _name; } } int main() { Student s0(15, 77, "John"); Student s1 = s0; // 복사 Student s2; s2 = s0; // 대입 }
대입 연산자의 반환값이 참조인 이유는 대입 연산자는 s0 = s1 = s2와 같은 연산도 가능해야 하며 이는 원본을 반환하는 것이기 때문입니다. 컴파일러가 암시적으로 만들어주는 디폴트 생성자, 복사 생성자, 대입 연산자는 오류가 발생하지 않는 최소입니다. 의도하는 뭔가가 있다면 반드시 구현해줘야 합니다.
'개발 > C·C++' 카테고리의 다른 글
[volatile] 컴파일러! 나대지 마라 (0) 2021.04.27 [const_cast] 제한적으로 활용하자 (0) 2021.04.27 [const member function] 이면에 있는 조금 더 복잡한 것 (0) 2021.04.22 객체 지향 개론 (0) 2021.04.21 [Build process] Three steps (0) 2021.04.19