오늘은 복사생성자에 대해 알아보겠습니닷
이 글은 포인터를 잘 모르시면 이해가 안될 수도 있습니다.
바로 ㄱㄱ싱
해당 코드를 실행하면 에러가 납니다. 왜 그런지 한번 생각해보시죠 ㅇㅅㅇ
#include <iostream>
class MyClass
{
public:
// 기본 생성자
MyClass() : m_data(nullptr) { std::cout << "기본 생성자 호출 ㅇㅅㅇ" << '\n'; }
// 파라미터 생성자
MyClass(const char* _str) {
int len = strlen(_str);
m_data = new char[len + 1];
strcpy_s(m_data, len + 1, _str);
std::cout << "파라미터 생성자 호출 ㅇㅅㅇ" << '\n';
}
// 소멸자
~MyClass()
{
delete[] m_data;
std::cout << "소멸자 호출 ㅇㅅㅇ" << '\n';
}
void Show() { std::cout << m_data << '\n'; }
private:
char* m_data;
};
int main()
{
MyClass mc1("Hello");
MyClass mc2 = mc1;
MyClass mc3;
mc3 = mc1;
mc1.Show();
mc2.Show();
mc3.Show();
return 0;
}
해당 코드의 문제점을 찾으셨나요?문제를 30초만에 바로 찾으셨다면 당신은 코딩고수....
뭐가 문제인데?
해당 코드의 문제는 mc1, mc2, mc3 총 3개의 객체들의 멤버변수인 m_data가 같은 메모리를 참조하고 있다는 것입니다. 즉 객체만 다르고 m_data는 같은 포인터를 공유하고 있다는 것임니다....
"니가 뭔데? 증거있음?" 이라고 하실까봐 위의 코드에서 m_data의 주소를 출력해 보겠읍니다..
// 출력 결과
파라미터 생성자 호출 ㅇㅅㅇ
기본 생성자 호출 ㅇㅅㅇ
Data : Hello, Address : 000001696D8337F0
Data : Hello, Address : 000001696D8337F0
Data : Hello, Address : 000001696D8337F0
소멸자 호출 ㅇㅅㅇ
말했듯이 같은 주소를 가리키고 있고, 소멸자도 전부 호출하지 못하며, 에러가 나고 꺼진 것을 볼 수 있습니다...
따라서 프로그램이 종료되기 직전 소멸자가 호출할 때, mc1의 소멸자가 호출되면서 m_data를 delete해주지만, mc2, mc3의 m_data도 같은 주소를 가리키기 때문에? mc1이 삭제된 직후 mc2, mc3가 삭제될때 이미 삭제된 메모리 삭제를 시도하기 때문에 터진답니다? ㅇㅅㅇ
어째서 같은 주소를 가리키는 것임?
그건 복사 생성자와 복사 대입 연산자의 결함때문인데요?
그것은 바로 사용자가 복사생성자를 명시적으로 정의하지 않으면 내부의 기본적으로 정의되어 있는 복사생성자를 호출하게 된답니다?
기본적으로 구현되어있는 생성자와 연산자들은 멤버를 각각 복사 대입하도록 구현되어 있기 때문인데요...
대체 어떻게 생겼길래?
복사 생성자의 내부 구현은 어떻게 되어있을까용
class MyClass {
char* m_data;
};
// 기본 복사 생성자의 내부 동작
MyClass::MyClass(const MyClass& _other)
{
// 이러면 m_data의 주소 값이 대입되므로 같은 주소를 가리킨다.
this->m_data = _other.m_data;
}
이런식으로 구현되어 있을거임(?) ㅇㅅㅇ
복사 대입 연산자도 이와 비슷한 맥락으로 이렇게 구현되어 있답니다?
class MyClass {
char* m_data;
};
// 기본 복사 생성자의 내부 동작
MyClass& MyClass::operator=(const MyClass& _other)
{
if (this != &other) // 자기 자신을 대입하는지 검사
{
// 위와 동일. 이러면 m_data의 주소 값이 대입되므로 같은 주소를 가리킨다.
this->m_data = _other.m_data;
}
return *this;
}
복사 생성자와 복사 대입 연산자는 대체 언제 호출됨?
둘의 차이는 생성 당시에 (=)연산자를 붙였는가 안붙였는가의 차이인듯 합니다람쥐
그렇다면 이런 현상을 어떻게 방지할 수 있는데?
그 해답은 바로 복사 생성자와 복사 대입 연산자의 재정의입니닷
// 복사 생성자 재정의
MyClass(const MyClass& _other)
{
CopyData(_other);
}
// 복사 대입 연산자 재정의
MyClass& operator=(const MyClass& _other)
{
// 복사 대상이 같으면 복사 ㄴㄴ
if (this != &_other)
{
CopyData(_other);
}
return *this;
}
// 멤버의 메모리 할당 후에 대상 카피
void CopyData(const MyClass& _other)
{
int len = strlen(_other.m_data);
m_data = new char[len + 1];
strcpy_s(m_data, len + 1, _other.m_data);
}
저같은 경우에는 이런 식으로 재정의해봤습니다 ㅇㅅㅇ
그럼 이제 제대로 출력되겠지?
바로 출력을 해보자.
// 출력 결과
파라미터 생성자 호출 ㅇㅅㅇ
복사 생성자 호출 ㅇㅅㅇ
기본 생성자 호출 ㅇㅅㅇ
복사 대입 연산자 호출 ㅇㅅㅇ
Data : Hello, Address : 00000177881C3700
Data : Hello, Address : 00000177881C40B0
Data : Hello, Address : 00000177881C3750
소멸자 호출 ㅇㅅㅇ
소멸자 호출 ㅇㅅㅇ
소멸자 호출 ㅇㅅㅇ
3개의 데이터가 의도대로 다른 주소를 가리키고, 에러 없이 소멸자도 정상적으로 잘 호출되는 모습이쥬?
오늘도 제가 꿀팁을 알려드렸는데요?
좋으면 구독과 좋아요 알림설정까지 꾸욱 ㅇㅅㅇ
'프로그래밍 > C++' 카테고리의 다른 글
[C++] 오늘의 삽질 (0) | 2024.09.13 |
---|---|
[C++] auto의 타입 추론에 대하여 알아보자.araboza (C++11) (0) | 2024.09.13 |
[C++] is_base_of<> 특정 클래스를 상속 받았는지 확인하는 함수 (2) | 2024.09.08 |
[C++] 생성자 상속(C++11) (1) | 2024.09.06 |
[C++] String 클래스 구현해보기 (0) | 2024.07.26 |