devRenio

함수와 참조, 복사 생성자

객체지향프로그래밍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();
}
  1. 값에 의한 호출로 객체 전달
    별도의 공간이 할당되기 때문에 person 객체의 복사 생성자 호출
  2. 객체 리턴
    객체 리턴 시 사본으로 리턴하기 때문에 mother 객체의 복사 생성자 호출
  3. 객체 생성 시 객체로 초기화
    son 객체의 복사 생성자 호출