공부/C++

8. 함수와 참조, 복사생성자

bokob 2024. 5. 14. 04:27

8.1 객체전달과 참조

'값에 의한 호출'로 객체 전달

  • 함수를 호출하는 쪽에서 객체 전달
    • 객체 이름만 사용
  • 함수의 매개 변수 객체 생성
    • 매개 변수 객체 생성
    • 매개 변수 객체의 공간이 스택에 할당
    • 호출하는 쪽의 객체가 매개 변수 객체에 그대로 복사됨
    • 매개 변수 객체의 생성자는 호출되지 않음 (복사 생성자가 실행됨)
  • 함수 종료
    • 매개 변수 객체의 소멸자 호출
  • 값에 의한 호출 시 매개 변수 객체의 생성자가 실행되지 않는 이유는? (복사 생성자 말하는 것이 아님)
    • 호출되는 순간의 실인자 객체 상태를 매개 변수 객체에 그대로 전달하기 위함

 

'값에 의한 호출' 방식으로 함수가 호출되는 과정

매개 변수 객체 c 생성될 때는 복사 생성자가 실행됨

 

 

'값에 의한 호출'시에 생성자와 소멸자의 비대칭 실행

매개변수로 받은 c에서 생성자가 실행되지 않고 복사 생성자가 실행됨

 

함수에 객체 전달 - '주소에 의한 호출'로

함수 호출시 객체의 주소만 전달

  • 함수의 매개 변수는 객체에 대한 포인터 변수로 선언
  • 함수 호출 시 생성자 소멸자가 실행되지 않는 구조

 

'주소에 의한 호출'로 함수가 호출되는 과정

 

객체 치환 및 객체 리턴

객체 치환

  • 동일한 클래스 타입의 객체끼리 치환 가능
  • 객체의 모든 데이터가 비트 단위로 복사
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이 됨

 

객체 리턴

#include <iostream>
using namespace std;
class Circle {
    int radius;
public:
    Circle() { radius = 1; }
    Circle(int radius) { this->radius = radius; }
    void setRadius(int radius) { this->radius = radius; }
    double getArea() { return 3.14 * radius * radius; }
};
Circle getCircle() {
    Circle tmp(30);
    return tmp; // 객체 tmp을 리턴, tmp 객체의 복사본이 리턴된다.
}
int main() {
    Circle c; // 객체가 생성된다. radius=1로 초기화
    cout << c.getArea() << endl;
    c = getCircle(); 
    // tmp 객체가 c에 복사된다. c의 radius는 30이 된다.
    // -> c가 tmp인 것이 아니라 안에 내용을 복사받은 것임
    cout << c.getArea() << endl;
}

/* 출력 결과
3.14
2826
*/

 

객체에 대한 참조

#include <iostream>
using namespace std;
class Circle {
    int radius;
public:
    Circle() { radius = 1; }
    Circle(int radius) { this->radius = radius; }
    void setRadius(int radius) { this->radius = radius; }
    double getArea() { return 3.14 * radius * radius; }
};
int main() {
    Circle circle;
    Circle& refc = circle; // circle 객체에 대한 참조 변수 refc 선언
    // 메모리 공간은 같지만, 이름이 여러개. 초기화를 해줘야 한다. 안하면 컴파일 에러 발생
    
    refc.setRadius(10);
    cout << refc.getArea() << " " << circle.getArea();
}

 

8.2 복사 생성자

C++에서 얕은 복사와 깊은 복사

얕은 복사(shallow copy)

  • 객체 복사 시, 객체의 멤버를 1:1로 복사
  • 객체의 멤버 변수에 동적 메모리가 할당된 경우
    • 사본은 원본 객체가 할당 받은 메모리를 공유하는 문제 발생

깊은 복사(deep copy)

  • 객체 복사 시, 객체의 멤버를 1:1 대로 복사
  • 객체의 멤버 변수에 동적 메모리가 할당된 경우
    • 사본은 원본이 가진 메모리 크기 만큼 별도로 동적 할당
    • 원본의 동적 메모리에 있는 내용을 사본에 복사
  • 완전한 형태의 복사
    • 사본과 원본은 메모리를 공유하는 문제 없음

 

C++에서 객체의 복사

 

객체 간의 초기화와 대입

같은 클래스의 객체 간에 서로 초기화나 대입이 가능

객체 간의 초기화: 복사 생성자 이용

  • 같은 클래스의 다른 객체와 같은 값을 갖도록 초기화
  • 클래스의 멤버 변수를 1대1로 초기화

객체 간의 대입: 대입 연산자 이용

  • 같은 클래스의 다른 객체의 값을 대입
  • 클래스의 멤버 변수를 1대1로 대입
Circle c1(10);
Circle c2 = c1; // ==Circle c2(c1): 복사 생성자를 통한 초기화
Circle c3;
c3 = c1;        // 객체 간의 대입
                // c1 객체를 c3 객체에 비트 단위 복사

 

복사 생성자

복사 생성자(copy constructor)란?

  • 객체의 복사 생성시 호출되는 특별한 생성자
  • 같은 클래스의 객체를 이용해서 초기화하는 생성자

특징

  • 한 클래스에 오직 1개만 선언 가능
  • 모양: 클래스에 대한 참조 매개 변수를 가지는 독특한 생성자

 

복사 생성 과정

복사 생성자와 객체 복사

#include <iostream>
using namespace std;
class Circle {
private:
    int radius;
public:
    Circle() { radius = 1; }
    Circle(int radius) { this->radius = radius; }
    Circle(Circle& c); // 복사 생성자 선언
    double getArea() { return 3.14 * radius * radius; }
};
Circle::Circle(Circle& c) { // 복사 생성자 구현
    this->radius = c.radius;
    cout << "복사 생성자 실행 radius = " << radius << endl;
}
int main() {
    Circle src(30); // src 객체의 보통 생성자 호출
    
    // dest 객체가 생성될 때 Circle(Circle& c)
    Circle dest(src); // dest 객체의 복사 생성자 호출
    
    cout << "원본의 면적 = " << src.getArea() << endl;
    cout << "사본의 면적 = " << dest.getArea() << endl;
}

/* 출력 결과
복사 생성자 실행 radius = 30
원본의 면적 = 2826
사본의 면적 = 2826
*/

 

디폴트 복사 생성자

개발자가 클래스에 복사 생성자를 작성해놓지 않으면

  • 컴파일러가 자동으로 디폴트 복사 생성자를 만들어서 삽입

 

디폴트 복사 생성자 사례

얕은 복사 문제가 발생하게 됨

메모리 공유하는데 다른 곳에서 이 객체를 값에 의한 호출을 하면 그 함수가 끝났을 때, 소멸자가 호출되게 되버린다.

 

얕은 복사 생성자를 이용하여 프로그램이 비정상 종료되는 경우

 

 

깊은 복사 생성자를 이용

깊은 복사를 하도록 새로 구현한 복사 생성자

class Person {
    // private: name; 이라 가정
    Person(const Person& person); // 복사 생성자
};
Person::Person(const Person& person) { // 복사 생성자
    this->id = person.id; // id 값 복사
    int len = strlen(person.name);// name의 문자 개수
    this->name = new char[len + 1]; // name을 위한 공간 핟당
    strcpy(this->name, person.name); // name의 문자열 복사
    // person.name이 가능한 이유는 복사 생성자여서 같은 클래스 타입이기 때문
}

 

묵시적 복사 생성에 의해 복사 생성자가 자동 호출되는 경우

void f(Person p) { // 2. 값에 의한 호출로 객체가 전달될 때, person 객체의 복사 생성자 호출
    p.changeName(“Kim Young woo");
}
Person g() {
    Person mother(2, “Hyun So Bin");
    return mother; // 3. 함수에서 객체를 리턴할 때, mother 객체의 복사본 생성. 복사본의 복사 생성자 호출
}
int main() {
    Person father(1, “Kim Min Soo");
    Person mother = father; // 1. 객체로 초기화하여 객체가 생성될 때, Person 객체의 복사 생성자 호출
    f(father);
    g();
}

 

'공부 > C++' 카테고리의 다른 글

10. 상속  (0) 2024.05.27
9. friend와 연산자 중복  (0) 2024.05.16
7. 여러가지 객체의 생성방법  (0) 2024.05.14
6. 접근지정자, const 객체, static 멤버  (1) 2024.04.18
5. 생성자와 소멸자  (0) 2024.04.11