함수와 참조, 복사 생성자
2025년 10월 22일 15시
카테고리 -
LECTURE,
객체지향프로그래밍II
객체지향프로그래밍II(김정준) 중간고사 대비 (5장)
함수의 인자 전달 방식
void swap(int a, int b) { ... } // call by value
...
swap(m, n);
void swap(int *a, int *b) { ... } // call by address
...
swap(&m, &n);
- 값에 의한 호출, call by value
◼ 함수가 호출되면 매개 변수가 스택에 생성됨
◼ 호출하는 코드에서 값을 넘겨줌
◼ 호출하는 코드에서 넘어온 값이 매개 변수에 복사됨
- 주소에 의한 호출, call by address
◼ 함수의 매개 변수는 포인터 타입
◼ 함수가 호출되면 포인터 타입의 매개 변수가 스택에 생성됨
◼ 호출하는 코드에서는 명시적으로 주소를 넘겨줌
◼ 기본 타입 변수나 객체의 경우, 주소 전달
◼ 배열의 경우, 배열의 이름
◼ 호출하는 코드에서 넘어온 주소 값이 매개 변수에 저장됨
값에 의한 호출로 객체 전달
void increase(Circle c) {
c.setRadius(c.getRadius() + 1); // 원본의 값 변경 X
} // 소멸자 호출 O
...
Circle donut(30);
increase(donut); // call by value, 생성자 호출 X
- 함수를 호출하는 쪽에서 객체 전달
◼ 객체 이름만 사용
- 함수의 매개 변수 객체 생성
◼ 매개 변수 객체의 공간이 스택에 할당
◼ 호출하는 쪽의 객체가 매개 변수 객체에 그대로 복사됨
◼ 매개 변수 객체의 생성자는 호출되지 않음
- 함수 종료
◼ 매개 변수 객체의 소멸자 호출
- 생성자 호출 X, 소멸자 호출 O : 생성자 소멸자 비대칭 실행 구조
- 값에 의한 호출 시 매개 변수 객체의 생성자가 실행되지 않는 이유?
◼ 호출되는 순간의 실인자 객체 상태를 매개 변수 객체에 그대로 전달하기 위함
주소에 의한 호출로 객체 전달
void increase(Circle *p) {
p->setRadius(p->getRadius() + 1); // 원본의 값 변경
} // 소멸자 호출 X
...
Circle donut(30);
increase(&donut); // call by address, 생성자 호출 X
- 함수 호출시 객체의 주소만 전달
- 함수의 매개 변수는 객체에 대한 포인터 변수로 선언
- 함수 호출 시 생성자 소멸자가 모두 실행되지 않는 구조
객체 치환과 객체 반환
Circle c1(5);
Circle c2(30);
c1 = c2; // c2 객체를 c1 객체에 비트 단위 복사. c1의 반지름 30
- 객체 치환
- 동일한 클래스 타입의 객체끼리 치환 가능
- 객체의 모든 데이터가 비트 단위로 복사
- 치환된 두 객체는 현재 내용물만 같을 뿐 독립적인 공간 유지
Circle getCircle() {
Circle tmp(30);
return tmp; // tmp 객체의 복사본 반환 (객체 반환)
}
...
Circle c; // c의 반지름 1
c = getCircle(); // tmp 객체의 복사본이 c에 치환. c의 반지름은 30이 됨
참조 변수
int n = 2;
int &refn = n; // 참조 변수 refn 선언. refn은 n에 대한 별명
Circle donut;
Circle &refc = donut; // 참조 변수 refc 선언. refc는 donut에 대한 별명
- 참조 변수 선언
- 참조자 &의 도입
- 이미 존재하는 변수에 대한 다른 이름(별명)을 선언
◼ 참조 변수는 이름만 생기며 새로운 공간을 할당받지 않음
◼ 초기화로 지정된 기존 변수의 메모리를 공유
int n = 2;
int &refn = n;
int *p = &refn; // 참조에 대한 포인터 변수 선언
int *p = &n // 동일한 선언
참조에 의한 호출
- call by reference
- 함수 형식
- 함수의 매개 변수를 참조 타입으로 선언
◼ 참조 매개 변수(reference parameter)라고 부름
◼ 참조 매개 변수는 실인자 변수를 참조함
◼ 참조 매개 변수의 이름만 생기고 공간이 생기지 않음
◼ 참조 매개 변수는 실인자 변수 공간 공유
◼ 참조 매개 변수에 대한 조작은 실인자 변수 조작 효과
void swap(int &a, int &b) { ... } // call by reference
...
swap(m, n);
참조 매개 변수가 필요한 사례
- 예시 : int average(int a[], int size)
- 평균을 반환하는 함수. 계산에 오류가 있으면 0 반환
- average 함수가 정상 평균으로 0을 반환하는 경우가 발생
-
오류인지 평균이 0인건지 판단 불가능
- 변경 : bool average(int a[], int size, int& avg)
- 참조 매개 변수 avg에 평균 값을 전달
- 오류 발생 여부는 반환값으로 판단
참조 리턴
char& find(char s[], int index) {
return s[index]; // 참조 리턴
}
...
char name[] = "Mike";
find(name, 0) = 'S'; // name[0]을 'S'로 변경. name == "Sike"
- C++의 함수 리턴
- C언어와 다르게 C++에서는 값 외에 참조 리턴 가능
- 참조 리턴
◼ 변수 등과 같이 현존하는 공간에 대한 참조 리턴
◼ 변수의 값을 리턴하는 것이 아님
얕은 복사와 깊은 복사
- 얕은 복사(shallow copy)
- 객체 복사 시, 객체의 멤버를 1:1로 복사
- 객체의 멤버 변수에 동적 메모리가 할당된 경우
◼ 사본은 원본 객체가 할당 받은 메모리를 공유하는 문제 발생
- 깊은 복사(deep copy)
- 객체 복사 시, 객체의 멤버를 1:1로 복사
- 객체의 멤버 변수에 동적 메모리가 할당된 경우
◼ 사본은 원본이 가진 메모리 크기 만큼 별도로 동적 할당
◼ 원본의 동적 메모리에 있는 내용을 사본에 복사
- 완전한 형태의 복사
◼ 사본과 원본은 메모리를 공유하는 문제 없음
복사 생성자
class Circle {
...
Circle(const Circle& c); // 복사 생성자 선언
};
...
Circle::Circle(const Circle& c) { // 복사 생성자 구현
this->radius = c.radius;
}
...
Circle src(30);
Circle dest(src); // 복사 생성자 호츨, dest의 radius == 30
- 복사 생성자(copy constructor)
- 특징
- 한 클래스에 오직 한 개만 선언 가능
- 복사 생성자는 보통 생성자와 클래스 내에 중복 선언 가능
- 클래스에 대한 참조 매개 변수를 가지는 생성자
디폴트 복사 생성자 (얕은 복사 생성자)
- 복사 생성자가 선언되어 있지 않은 클래스에 컴파일러가 자동으로 삽입
- 원본 객체의 각 멤버를 사본 객체의 각 멤버에 그대로 복사(얕은 복사)
- 생성자에서 멤버를 동적 할당하고 소멸자에서 반환한 경우
- 복사 후 소멸자 호출 시 동일한 메모리를 여러 번 반환하여 오류 발생
깊은 복사 생성자
- 복사 생성자를 명시적으로 선언하여 사본을 위한 별도 메모리를 동적 할당
- 사본이 별도의 메모리를 할당받기에 여러 번 반환하는 오류가 발생하지 않음
복사 생성자가 자동으로 호출되는 사례
void f(Person person) { // 1. 값에 의한 호출로 객체 전달
person.changeName("dummy");
}
Person g() {
Person mother(2, "Jane");
return mother; // 2. 객체 리턴
}
int main() {
Person father(1, "Kitae");
Person son = father; // 3. 객체 생성 시 객체로 초기화
f(father);
g();
}
- 값에 의한 호출로 객체 전달
별도의 공간이 할당되기 때문에 person 객체의 복사 생성자 호출
- 객체 리턴
객체 리턴 시 사본으로 리턴하기 때문에 mother 객체의 복사 생성자 호출
- 객체 생성 시 객체로 초기화
son 객체의 복사 생성자 호출