[C++] - 2일차(생성자, 소멸자)

Jun·2026년 2월 17일

c++

목록 보기
2/6

생성자, 소멸자 그리고 객체지향의 핵심 (this, Getter/Setter)

1. 생성자와 소멸자 (Constructor & Destructor)

클래스에서 객체가 생성될 때와 사라질 때 자동으로 실행되는 특별한 함수들입니다.

생성자 (Constructor) 재설명

  • 정의: 객체가 메모리에 태어나는 순간 자동으로 실행되는 특별한 함수입니다.

  • 목적: 아기가 태어나면 이름을 지어주듯, 객체가 태어날 때 변수들에 첫 번째 값(초기값)을 넣어주는 역할을 합니다.

  • 특징: 클래스 이름과 똑같이 생겼고, 우리가 직접 부르지 않아도 MyClass m1;이라고 쓰는 순간 컴퓨터가 알아서 실행합니다.

소멸자 (Destructor)

  • 특징: 클래스 이름 앞에 ~를 붙입니다.
  • 실행 시점: 객체가 사라지는 순간(함수가 종료되거나 프로그램이 끝날 때) 자동으로 호출됩니다.

실행 순서 분석 (지역 객체 vs 전역 객체)

1) 지역 객체의 경우
함수 내부에서 생성된 객체는 해당 함수({ })가 끝나는 순간 소멸합니다.

    class MyClass {
    public:
        MyClass() { cout << "생성자 호출" << endl; } // (3)
        ~MyClass() { cout << "소멸자 호출" << endl; } // (5)
    };

    void testObj() {
        cout << "testObj() 함수 호출 중..." << endl; // (2)
        MyClass obj2; // 생성자 호출
        cout << "testObj() 함수 끝..." << endl; // (4)
    } // obj2 소멸자 자동 호출 시점

    int main() {
        cout << "=== main 시작 ===" << endl; // (1)
        testObj();
        cout << "=== main 종료 ===" << endl; // (6)
    }

2) 전역 객체의 경우
전역 공간에 선언된 객체는 프로그램 시작 시 가장 먼저 생성되고, 프로그램이 완전히 종료될 때 소멸합니다.

	class MyClass {
	public:
		MyClass() {
			cout << "생성자 호출" << endl;  //(1)
		}

		~MyClass() { 
			cout << "소멸자 호출" << endl;  //(6) 
		}


	};

	MyClass obj; // 0) 전역 객체: main보다 먼저 생성!


	void testObj() {
		cout << "testObj() 함수 호출 중..." << endl; //(3)
		cout << "testObj() 함수 끝..." << endl;     //(4)
	} // obj2 소멸자 자동 호출




	int main(void) {
		cout << "=== main() 함수 시작 ===" << endl;  //(2)
    
		testObj(); // 객체 생성  -> 생성자는 자 동 호출 (void testObj로)
    
		cout << "=== main() 함수 종료 ===" << endl; //(5)
	}
    	

2. 'this' 포인터의 비밀

컴퓨터는 내부적으로 멤버 함수를 호출할 때 "어떤 객체가 나를 불렀는지"를 알기 위해 객체의 주소를 몰래 넘겨줍니다.

  • 실제 모습: void printData()void printData(MyClass* this)
  • 역할: data라고만 써도 컴퓨터는 this->data로 해석하여 호출한 객체의 데이터를 정확히 찾아갑니다.
int main() {
    MyClass a, b; // 객체 a와 b가 만들어졌어요.
    a.data = 10;
    b.data = 20;

    a.printData(); // (A) 호출
    b.printData(); // (B) 호출
}

- (A) a.printData();를 실행할 때 
컴퓨터는 a의 메모리 주소(: 0x100)를 파악합니다.
printData 함수를 실행하면서 몰래 this = &a; 라고 주소를 넘겨줍니다.
함수 안에서 cout << data;를 만나는 순간, 컴퓨터는 this가 가리키는 a의 주소로 가서 10을 꺼내옵니다.

- (B) b.printData();를 실행할 때 (이미지의 &b 부분)
이번엔 b의 메모리 주소(: 0x200)를 파악합니다.
똑같은 printData 함수를 쓰지만, 이번엔 몰래 this = &b; 라고 주소를 바꿔서 넘겨줍니다.
함수 안에서 this는 이제 b를 가리키므로, 20을 꺼내오게 됩니다.

3. 정보 은닉: Getter와 Setter

클래스 내부의 변수(멤버 변수)를 private에 숨겨 안전하게 보호하고, 외부에서는 정해진 함수(public)를 통해서만 접근하게 만드는 방식입니다.

  • Getter (열람용): "금고 안의 데이터를 복사해서 보여만 줄게."
  • Setter (설정용): "외부에서 가져온 값을 검사한 뒤 안전할 때만 넣어줄게."
    class MyClass {
    private:
        int real; // 외부 접근 불가 (금고)

    public:
        int getReal() { return real; } // Getter
        void setReal(int r) { real = r; } // Setter
    };

통합 예제

class MyClass {
	// 1. 외부에서 사용해야 하는 생성자와 함수는 반드시 public에 선언합니다.
public:
	// 기본 생성자
	MyClass() {
		real = 0;
		imag = 0;
	}

	// 매개변수가 있는 생성자
	MyClass(int r, int i) {
		real = r;
		imag = i;
	}

	// Getter / Setter
	int getReal() { return real; }
	void setReal(int r) { real = r; }

	int getImag() { return imag; }
	void setImag(int i) { imag = i; }

	// 2. 실제 데이터(멤버 변수)는 안전하게 private에 보관합니다.
private:
	int real;
	int imag;
};

int main(void) {
	// 객체 생성 방법 3가지
	MyClass m1;                // 기본 생성자 호출 (real=0, imag=0)
	MyClass m2 = MyClass(5, 6); // 명시적 생성
	MyClass m3(7, 8);          // 암시적 생성 (가장 많이 쓰임)

	cout << "m1: " << m1.getReal() << " + " << m1.getImag()  << endl;
	cout << "m2: " << m2.getReal() << " + " << m2.getImag()  << endl;
	cout << "m3: " << m3.getReal() << " + " << m3.getImag()  << endl;

	return 0;
}

4. 생성자 오버로딩 (Overloading)

이름은 같지만 매개변수(재료)의 개수나 타입을 다르게 하여 여러 개의 생성자를 만드는 기술입니다.

class MyClass {
public:
    // 1번 생성자: 매개변수(재료)가 0개
    MyClass() {
        real = 0; imag = 0;
    }

    // 2번 생성자: 매개변수(재료)가 2개 (int, int)
    // 이름은 1번과 똑같지만 매개변수 개수가 틀리죠? -> 오버로딩!
    MyClass(int ral, int img) {
        real = ral; imag = img;
    }
};



MyClass m1; // 이라고 쓰면 컴퓨터가 1번을 호출합니다.

MyClass m2(5, 6); //이라고 쓰면 재료가 2개니까 2번을 호출합니다.

데이터 타입이 틀린 경우
재료의 개수가 같아도 종류(타입)가 다르면 오버로딩이 됩니다.

MyClass(int a) : 정수를 받는 버전

MyClass(double a) : 실수를 받는 버전
  • 컴퓨터는 함수를 찾을 때 이름만 보는 게 아니라,
    (이름 + 매개변수 구성)을 통째로 하나의 세트로 봅니다.

  • Student() 와 Student(int score) 는 이름은 같지만, 컴퓨터 입장에서는
    "재료가 없는 버전""숫자 재료가 하나 필요한 버전"으로 확실히 구분해서
    인식할 수 있습니다.

즉 "오버로딩은 이름은 하나로 통일하되, 재료(매개변수)의 '개수'나 '타입'을 다양하게 준비해서
여러 상황에 대처하는 기술이다!"


  • 주의사항: 매개변수가 있는 생성자를 하나라도 직접 만들면, 컴파일러는 디폴트 생성자(매개변수 없는 것)를 자동으로 만들어주지 않습니다. 필요하다면 직접 선언해야 합니다.
class MyClass {
public:
    // 1. 내가 특별한 메뉴(매개변수 생성자)를 하나 만들었습니다.
    MyClass(int a) { 
        cout << "숫자 전달받음!" << endl; 
    }
};

int main() {
    MyClass m1; // 2. 에러 발생! "기본 메뉴(디폴트 생성자)가 없는데요?"
}
  • 아무것도 안 했을 때 : (디폴트 생성자 자동 생성): 분식집에 메뉴판이 비어있으면,
    주인은 손님이 올 때를 대비해 아주 기본적인 '그냥 김밥' 하나를 미리 준비해 둡니다.
    그래서 우리는 아무 말 없이 "김밥 하나요!" 해도 김밥을 먹을 수 있습니다.

  • 특별한 메뉴를 만들었을 때 (매개변수 생성자 선언): 우리가 메뉴판에 '참치 김밥(5,000원)'이라는 특별한 메뉴를 딱 적어 넣는 순간! 주인은 "오, 이제 이 집은 고급 참치 김밥 전문점이구나?
    이제 그냥 김밥은 안 만들어야지."

  • 문제가 생기는 상황: 이때 어떤 손님이 와서 그냥 "김밥 하나요!(MyClass m1;)"라고 하면, 주인님은 "우리 집엔 이제 참치 김밥밖에 없어요! 그냥 김밥은 안 팔아요!"라고 화를 냅니다. 이게 바로 에러가 나는 상황입니다.


5. 생성자 위임 (Constructor Delegation)

비슷한 초기화 코드가 중복되는 것을 방지하기 위해, 하나의 생성자가 다른 생성자를 호출하는 방식입니다.

[생성자 위임 방식 (효율적)]
초기화 리스트(:)를 사용하여 다른 생성자에게 일을 맡깁니다.

#include <iostream>
using namespace std;

class Time {
private:
    int h, m, s;

public:
    // 1. 기본 생성자: 모든 값을 0으로 초기화
    Time() : h(0), m(0), s(0) {
        cout << "기본 생성자 호출" << endl;
    }

    // 2. 생성자 위임: Time()을 먼저 불러서 0으로 세팅한 뒤 s만 변경
    Time(int s) : Time() {
        this->s = s;
        cout << "Time(int s) 호출" << endl;
    }

    // 3. 생성자 위임: Time(s)를 불러서 처리한 뒤 m만 추가로 변경
    Time(int m, int s) : Time(s) {
        this->m = m;
        cout << "Time(int m, int s) 호출" << endl;
    }

    // 4. 모든 값을 직접 받는 생성자
    Time(int h, int m, int s) : Time(m,s){
    	this->h = h;
    }

   
};

[노가다 방식 (비효율적)]
매번 모든 변수를 일일이 대입해줘야 하므로 코드가 길어지고 실수가 생길 수 있습니다.

#include <iostream>
using namespace std;

class Time {
private:
    int h, m, s;

public:
    // 1. 기본 생성자: 모든 값을 0으로 초기화
    Time() : h(0), m(0), s(0) {
        cout << "기본 생성자 호출" << endl;
    }

    // 2. 생성자 위임: Time()을 먼저 불러서 0으로 세팅한 뒤 s만 변경
    Time(int s)  {
    	h = 0;
        m = 0;
        this->s = s;
    }

    // 3. 생성자 위임: Time(s)를 불러서 처리한 뒤 m만 추가로 변경
    Time(int m, int s) {
    	h=0;
        this->m = m;
        this->s = s;
    }

    // 4. 모든 값을 직접 받는 생성자
    Time(int h, int m, int s) {
    	this->h = h;
        this->m = m;
        this->s = s;
    }

   
};

요약: 외부(main 함수 등)에서 클래스 내부의 private 영역은 직접 건드릴 수 없으므로, 생성자나 Getter/Setter라는 '공식적인 통로'를 이용해 소통해야 합니다.

profile
Hard Trying

0개의 댓글