2025. 5. 3. 15:11ㆍProgramming Language/C++
먼저 복사 생성자에 대해서 알아보도록 하자.
class Person {
private:
string name;
public:
Person() = default;
Person(const char* c)
:name(c)
{
}
// 복사 생성자
Person(const Person& other) {
name = other.name;
}
};
이런 클래스가 존재한다고 할때
int main() {
Person p1("Alice");
Person p2 = p1;
Person p1("Alice");
Person p2;
p2 = p1;
}
여기에서
Person p2 = p1;
와
Person p2;
p2 = p1;
는 다르다.
위의 Person p2 = p1; 의 경우는 복사 생성자를 호출해서 사용하는 것이고 Person p2; p2 = p1; 의 경우는 대입 연산을 수행한 결과 이다.
기본적으로 복사생성자와 대입연산의 경우는 디폴트로 구현이 되어 있어 위 처럼 궂이 만들어주지 않더라도
#pragma warning(disable:4996)
#include <iostream>
#include <string>
using namespace std;
class Person {
private:
string name;
public:
Person() = default;
Person(const char* c)
:name(c)
{
}
void print() {
std::cout << m_name << std::endl;
}
};
int main() {
Person p1("Alice");
Person p2 = p1; // 복사 생성자 호출 - 문제 없음
Person p3("Brown");
Person p4;
p4 = p3; // 대입 연산자 - 문제 없음
}
이렇게 정상적으로 컴파일 되는 것을 볼 수 있다.
근데 만약에 우리가 name을 string이 아니라 char 배열로 받는다면 크기를 지정해줘야하고
class Person {
private:
char m_name[9];
public:
Person() = default;
Person(const char* name)
:m_name()
{
strcpy(m_name, name);
}
void print() {
std::cout << m_name << std::endl;
}
};
이때 호출부에서 이 크기를 벗어난 값을 받는다면
int main() {
Person p1("Alice Jame Kim");
Person p2 = p1;
}
에러를 발생시킨다.
그렇기에 이 크기를 동적으로 할당 받기 위해 타입을 char* 타입으로 변경하고 생성자 이니셜라이즈 리스트에서 동적으로 공간을 할당 받은 다음에 데이터를 복사해주도록 해주면
class Person {
private:
char* m_name; // 동적 할당을 위해 타입변경
public:
Person() = default;
Person(const char* name)
:m_name(new char[strlen(name) + 1]) // 동적 할당받을 크기 지정 및 동적 할당
{
strcpy(m_name, name); // 값 복사
}
void print() {
std::cout << m_name << std::endl;
}
};
int main() {
Person p1("Alice Jame Kim");
Person p2 = p1;
}
문제가 없이 실행되는 것처럼 보인다.
그렇지만 저렇게 만들어주면 동적할당된 name의 공간을 해제해줘야 하기 때문에 소멸자를 만들어 해당 공간을 소멸시켜줘야한다.
class Person {
private:
char* m_name;
public:
Person() = default;
Person(const char* name)
:m_name(new char[strlen(name) + 1])
{
strcpy(m_name, name);
}
// 동적 할당된 공간을 소멸시킬 소멸자 생성
~Person() {
delete[] m_name;
}
void print() {
std::cout << m_name << std::endl;
}
};
int main() {
Person p1("Alice Jame Kim");
Person p2 = p1;
}
이렇게 실행해보면
또 문제가 있단다;
디버깅을 사용해서 이 문제의 시작점을 확인해보면
보면 복사된 m_name의 주소값이 동일한 것을 볼 수 있다.
이렇게 복사가 될때 얕은복사, 주소값만 복사해서 넣어줘서 발생하는 문제이다.
그래서 우리는 깊은 복사를 해야하고 복사 생성자를 사용해야하는 이유가 바로 이것이다.
복사 생성자는
Person(const Person& person) {
}
이렇게 매개변수로 const Person& person 타입의 매개변수를 받아주고 이제 생성자 이니셜라이저 리스트를 사용해서 동적할당을 해준 후에 내부에서 strcpy로 값을 복사해주면 된다.
Person(const Person& person)
:m_name(new char[strlen(person.m_name) + 1])
{
strcpy(m_name, person.m_name);
}
이제 실행해보면
이렇게 주소값이 각각 다른것을 볼 수 있다.
추가로 생성자 이니셜라이저 말고 생성자 위임이라는 방식을 사용해서도 사용이 가능하다.
Person(const Person& person)
:Person(person.m_name) // 생성자 위임, Person(name) 생성자를 부르는것과 동일
{
}
이렇게 되면 내부에 값 복사도 할 필요가 없어진다.
동일하게 작동하는 것을 볼 수 있다.
이제 대입 연산자 오버로딩을 해보자.
먼저 대입 연산의 경우는 아래 처럼 연속적으로 대입하는 경우도 있기 때문에
Person p1{ "Alice Jame Kim" };
Person p2;
Person p3;
p3 = p2 = p1; // 연속적으로 대입하기도함
대입 연산자 오버로딩 함수의 반환값으로 그 객체의 타입을 그대로 반환해줘야 한다.
Person& operator=(const Person& p) {
}
그리고 이제 내부에서 값을 일일이 대입해주면 된다.
Person& operator=(const Person& p) {
m_name = new char[strlen(p.m_name) + 1]; // 동적으로 공간할당
strcpy(m_name, p.m_name); // 값 복사
}
그런데 이렇게만 해주면 되는게 아니라 이게 대입이기에 이전에 초기화시점에 만들어 졌던 동적할당된 공간을 지워주면서 새로 공간을 할당해줘야한다.
Person& operator=(const Person& p) {
delete[] m_name; // 생성자로 만들었던 동적할당 공간 제거
m_name = new char[strlen(p.m_name) + 1]; // 새롭게 전달 받은 대입할 문자열만큼의 공간 생성
strcpy(m_name, p.m_name); // 값 복사
}
그리고 이렇게 한 후에 마지막으로 연속 대입 연산이 가능하게 하기 위해 *this를 반환해준다.
Person& operator=(const Person& p) {
delete[] m_name;
m_name = new char[strlen(p.m_name) + 1];
strcpy(m_name, p.m_name);
return *this;
}
이러고 대입 연산을 실행해보면
int main() {
Person p1{ "Alice Jame Kim" };
Person p2 = p1;
p1.print();
p2.print();
Person p3, p4, p5;
p5 = p4 = p3 = p2;
p3.print();
p4.print();
p5.print();
}
주소값이 각자 다 다르게
결과를 출력하는 것을 볼 수 있다.
**복사가 일어나는지 확인하는 방법
이전에 복사가 일어나는지 안일어나는지에 대해서 확인하지 않고 & 참조로 넘어가면 복사가 안되고 그냥 value로 넘어가면 복사가 일어난다고 했던 부분을 복사 생성자에 console을 찍으면서 확인이 가능하다.

복사생성자에 이렇게 로그를 찍어주고

단순하게 Person& 참조값을 매개변수로 받는 함수를 한번 만들고

이렇게 함수로 던져주면 p1이 &참조로 받아지면서 복사가 일어나지 않는다.

이걸 참조가 아닌 Person 타입으로 받으면


이렇게 복사가 일어나는 것을 확인할 수 있다.
근데 또 반환타입이 Person이라면

전달하는 타입과는 상관없이 return하면서 복사가 일어나게 된다.

이것도 반환을 & 참조로 변경하면


복사가 일어나지 않는다.
또한 내부에서 객체를 만들어서 반환하면


복사가 일어나지 않는다.
이런 점들에 대해서 알아두고 나중에도 테스트 해보면서 언제 복사가 일어나는지 파악해두면 좋을듯 싶다
복사가 일어나는 경우
- 함수의 매개변수의 타입이 참조가 아닌 경우
- 반환형의 타입이 참조가 아닌경우
복사가 일어나지 않는 경우
- 함수의 매개변수의 타입이 참조인 경우
- 반환형의 타입이 참조인 경우
- 내부에서 생성한 객체를 참조형이 아닌 타입으로 반환하는 경우
'Programming Language > C++' 카테고리의 다른 글
Part2::Ch 02. 연산자 오버로딩- 09. 호출 연산자 오버로딩, 함수 객체 (0) | 2025.05.03 |
---|---|
Part2::Ch 02. 연산자 오버로딩- 08. 변환 연산자 오버로딩, 변환 생성자, explicit (0) | 2025.05.03 |
Part2::Ch 02. 연산자 오버로딩- 06. 첨자 연산자 오버로딩 (0) | 2025.05.02 |
Part2::Ch 02. 연산자 오버로딩- 05. 비트 연산자 오버로딩 (0) | 2025.05.02 |
Part2::Ch 02. 연산자 오버로딩- 04. 논리 연산자 오버로딩 (0) | 2025.05.02 |