F.R.I.D.A.Y.

클래스의 초기화 순서 본문

DEV/C C++

클래스의 초기화 순서

F.R.I.D.A.Y. 2019. 1. 28. 21:18
반응형

 C언어의 구조체(struct) 문법이 강화되면서 C++에 클래스 문법이 만들어졌습니다. 이 클래스 문법은 클래스 생성자가 존재합니다. 생성자를 간단히 설명하면 클래스를 인스턴스 해 객체를 만드는 과정에서 만들어진 객체가 오작동 없이 잘 기능할 수 있도록 내부 데이터(멤버 변수)를 초기화해주는 함수라고 생각하면 될것 같습니다.

 


1. class의 기본 구조

 

#include <iostream>

class test {
private:
	int a; // 멤버 변수들
	int b;
	int c;

public:
	test() {
		// 생성자
	}

	~test() {
		// 소멸자
	}
};

 클래스의 기본 구조는 위와 같습니다. 생성자는 클래스 이름과 같고, 괄호 안에는 매개변수가 없거나 하나 이상 들어갈 수 있습니다. C++의 함수 오버로딩 기능이 이를 가능케 한것입니다. 만일, 매개변수를 하나도 작성하지 않는다면 해당 생성자는 default(기본) 생성자가 됩니다. 소멸자는 기본 소멸자 하나만 선언할 수 있습니다.

 소멸자는 객체의 수명(Life Cycle)이 만료되었을 때, 즉 더이상 사용하지 않을 때 할당받은 메모리 등을 해제할 때 사용합니다. 만일, 객체 내부에서 할당받은 메모리를 소멸자에서 해제하지 않으면 해당 메모리 영역은 프로그램이 종료될 때까지 메모리 누수가 발생합니다.

더보기

# 참고

 현재의 OS는 프로그램이 종료되면 할당한 메모리를 전부 빼앗아 가기 때문에 프로그램상에서 메모리 누수가 발생하더라도 프로그램이 종료되면서 누수된 메모리가 복구됩니다. 그러나 이런식으로 메모리 처리를 하는 경우는 대단히 좋지 못한 방법입니다.


2. 멤버 변수의 초기화 순서

 객체를 처음 선언하면 전달받은 매개변수에 맞게 생성자가 호출됩니다. 아래 코드를 예시로 들겠습니다.

 

class test {
private:
	int a; // 멤버 변수들
	int b = 5;
	int c;

public:
	test() {
		// 생성자
	}
	test(int val_a) {
		a = val_a;
	}
	~test() {
		// 소멸자
	}
};

int main(void) {
	test t1;    // test() 생성자 호출
	test t2(5); // test(int val_a) 생성자 호출

	return 0;
}

 즉, 매개변수 개수와 자료형에 맞는 생성자를 호출하는 것을 볼 수 있습니다.

 잠시 프라이빗 접근자의 b 변수를 보면, b = 5로 선언되어 있습니다. 즉, class에서는 대입하는 방법으로 변수 초기화가 가능합니다. class 문법에서는 멤버 변수를 초기화하는 여러가지 방법이 있습니다.

 

private:
    int var = 5; // 멤버 변수 선언과 동시에 초기화

//--------------------------------------------------------------

test(int val_v) : var(val_v) {} // 생성자 부분에서 초기화 리스트를 통해 초기화

//--------------------------------------------------------------

test(int val_v) {
    var = val_v; // 생성자 내부에서 전달받은 매개변수를 대입하며 초기화
}

위와 같은방법이 있는데, 만일 그렇다면 위 세가지 방법 중, 두가지 이상 사용하면 어떻게 처리가 될까요?

 

 이와같은 의문점을 확인하기 위해 다음과 같이 코드를 작성해 보았습니다.

 

#include <iostream>

class test {
private:
	int a = printf("멤버 변수 a에 직접 대입하며 초기화\n"); // 멤버 변수들
	int b = printf("멤버 변수 b에 직접 대입하며 초기화\n");
	int c;

public:
	test() {
		// 생성자
	}
	test(int val_a) : a(printf("초기화 리스트에서 a 초기화\n")){
		a = printf("생성자 내부에서 a 초기화\n");
	}
	~test() {
		// 소멸자
	}
};

int main(void) {
	test t1;
	test t2(5);

	return 0;
}

 C++의 표준 입출력은 cout이지만, printf가 반환값 int인 함수이기 때문에 printf를 사용했습니다.

 위 코드를 컴파일해 실행하면 아래 이미지와 같이 나옵니다.

Visual Studio에서 컴파일 한 프로그램

더보기

 + 2019.10.27.

 글에서 오류가 확인되어 고치면서 생각해보니 오류의 원인이 아마도 두 개의 객체를 한번에 생성하며 구분되는 줄을 만들지 않아 혼동을 한것 같습니다. 그에 따라 다시 컴파일을 진행한 결과로는 아래 이미지를 확인하실 수 있습니다. 아래 이미지는 위와 동일한 코드에서 단순히 줄 띄어쓰기만 추가한 결과입니다.

https://www.onlinegdb.com/online_c++_compiler

 


3. 결론

 C++의 클래스는 초기화 리스트로 초기화를 처음으로 진행하며, 초기화 리스트에 존재하지 않는 멤버 변수를 추가로 초기화를 진행합니다. 마지막으로 생성자 내부에서 값을 대입하는 방법으로 초기화를 진행합니다.

 

  1.  초기화 리스트에 들어있는 멤버 변수 초기화
  2.  초기화 리스트에서 초기화되지 않은 항목들 중, 값이 존재하는 멤버변수를 초기화
  3.  생성자 내부에서 값을 초기화

4. 초기화 리스트는 왜 있을까?

 그렇다면 초기화 리스트는 왜 있을까요? 멤버 변수에 직접 값을 대입해서 초기화하면 안되는가 싶습니다. 아래 코드를 보겠습니다.

 

class test {
private:
	const int const_a = 5;

public:
	test() {
		// 생성자
	}

	~test() {
		// 소멸자
	}
};

 이렇게 만들어졌을 때, test 객체에 존재하는 모든 const_a의 값은 5로 고정됩니다. 그런데 모든 객체에 동일한 값이 상수로 들어가는 경우가 있을까요? 물론 상수가 변하지 않도록 const로 고정을 시켜놓긴 했지만, 종종 경우에 따라 상수변수인 const_a의 값이 달라져야할 필요가 있을 수 있습니다. 멤버 변수에 직접 초기화하는 방식을 사용하면 숫자나 문자열과 같이 상수외에는 사용할 수 없습니다. 그러나 초기화 리스트를 사용하면 객체를 생성하며 넘겨진 매개 변수를 const_a에 대입이 가능합니다. 따라서 const의 값을 설정하고 싶다면 초기화 리스트를 사용해 초기화 할 수 있습니다. 이 경우 1회에 한해 변경이 가능하다.

 

 초기화 리스트를 사용하는 이유

> 초기화 리스트는 상수 멤버의 값 변경에 이용하기 위해 존재

 

#include <iostream>

class test {
private:
	const int const_a;

public:
	test() : const_a(5) {};

	test(int a) : const_a(a) {
		// 생성자
	}

	void print() {
		std::cout << "const_a의 값은 " << const_a << "입니다." << std::endl;
	}

	~test() {
		// 소멸자
	}
};

int main(void) {
	test t1;
	test t2(10);

	t1.print();
	t2.print();

	return 0;
}

 output :

const_a의 값은 5입니다.

 

const_a의 값은 10입니다.

https://www.onlinegdb.com/online_c++_compiler

 

728x90
반응형

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

홀수 마방진 풀기  (0) 2019.03.09
시프트 연산자  (2) 2019.01.29
변수(variables)  (0) 2019.01.25
클래스(class)  (0) 2019.01.25
Q strlen 구현하기  (0) 2019.01.25
Comments