본문 바로가기
IT/C++(CPP)

CPP 초급 강좌 28. 생성자 constructor

by 신림83 2020. 11. 9.
반응형

CPP 초급 강좌 28. 생성자 constructor

 이전에도 생성자에 대해 보았지만, 조금 더 자세히 보겠습니다.

 

아래 소스를 봅시다.

class Point {
private:
	int x, y;

public:
	Point()
	{
		x = y = 0;
	}
	Point(int x, int y)
	{
		this->x = x;
		this->y = y;
	}
};


int main()
{
	Point p1;
	Point p2(0, 0);
}

Point 클래스를 제작하고 생성자 인자 없는 버전과 인자가 둘인 버전을 제작하였습니다.

그리고 main 함수에서 둘을 호출하죠.

 

생성자 또한 오버로딩이 가능합니다.

 

위는 문제없는 코드입니다.

 

위의 코드에서 

Point()
{
  x = y = 0;
}

 

생성자 코드를 제거한다고 가정하여 봅시다.

 

p1에서 문제가 발생합니다.

에러

인자가 없는 생성자를 호출할 수 없기 때문인데 그럼 아래 코드는 어떨까요?

class Point {
private:
	int x, y;
public:
};


int main()
{
	Point p1;
}

 

디폴트 생성자

위의 코드는 문제가 없습니다. 왜죠? 생성자가 없는데 어떻게 구동이 되는 거죠?

 클래스에 코딩되어 있는 생성자가 하나도 없을 경우, 컴파일러는 자동으로 디폴트 생성자를 생성시킵니다. 인자 없는 생성자가 생성됩니다. 그래서 p1 코드가 문제없이 작동되는 것입니다.

 

 특정 버전을 생성자(인자수가 많다거나)를 생성하면서, 컴파일러에 디폴트 생성자를 요청하는 기술이 있습니다.

아래와 같이 표현하면 됩니다.

class Point {
private:
	int x, y;

public:
	Point() = default;
	Point(int x, int y) { this->x = x; this->y = y; }
	~Point() {}
};

해당 표현의 장점은, 선언부 구현부가 나눠질 시, 구현부에 디폴트 생성자를 표기하지 않아야 한다는 것 정도?

위임 생성자 delegate constructor

class Point {
private:
	int x, y;

public:
	Point() { x = 0; y = 0; }
	Point(int x, int y) { this->x = x; this->y = y; }

	~Point() {}
};

 위와 같은 경우 인자가 없는 생성자는 사실 아래 인자가 둘인 생성자를 이용해서 초기화 할수도 있다. 생성자가 다른 생성자를 부를 수 있는 기술이 c++11부터 생겼다.

class Point {
private:
	int x, y;

public:
	Point() : Point(0,0) //여기 수정
	{
		//... 여기 코드는 일자 둘인 생성자 실행이후 실행됨
	}

	Point(int x, int y) { this->x = x; this->y = y; }

	~Point() {}
};

Point() : Point(0,0)  이 코드와 같이 생성자 호출시 뒤에 다음과 같이 다른 생성자를 표시해 주면 호출할 수 있다. 그리고 자신의 함수에서 구현된 부분은 호출한 생성자 코드가 호출된 뒤, 실행되는 구조이다.

 

해당 코드를 선언부, 구현부를 나눌시에는 다음과 같이 해야 한다.

선언

Point();

구현

Point::Point() : Point(0, 0) {}

구현 쪽에만 대리 생성자 코드를 추가하여야 합니다.

 

다양한 생성자 호출 방법

class Point {
private:
	int x, y;

public:
	Point()	//1번 생성자
	{
		x = y = 0;
	}
	Point(int x, int y) //2번 생성자
	{
		this->x = x;
		this->y = y;
	}
};

인자 없는 생성자를 1번 생성자 Point(), 인자가 둘 있는 생성자를 2번 생성자 Point(int x, int y)라고 하겠습니다.

다음 객체 생성 코드를 봅시다.

 

1번 생성자를 호출하는 방법

Point p1;      //a
Point p2{};    //b
Point p3 = {}; //c
	
Point p4();    //d

a 1번 생성자를 호출합니다. 많이 보신 코드입니다.

b 1번 생성자를 호출합니다. c++ 11 부터 등장한 일괄된 초기화 기술입니다.

c 1번 생성자를 호출합니다. c++ 11 부터 등장한 일괄된 초기화 기술입니다.

d 는 함수로 전언되었습니다. Point를 반환하는 p4라는 인자 없는 함수입니다. 주의합시다.

 

2번 생성자를 호출하는 방법

Point p1(1,2);    //a
Point p2{1,2};    //b
Point p3 = {1,2}; //c

a 2번 생성자를 호출합니다. 많이 보신 코드

b 2번 생성자를 호출합니다. c++ 11부터 등장한 일괄된 초기화 기술입니다.

c 2번 생성자를 호출합니다. c++ 11부터 등장한 일괄된 초기화 기술입니다.

 

배열로 생성한다면

Point p1[3];                       //a
Point p2[3] = { Point(1,1) };      //b
Point p3[3] = { {1, 2},  {1, 2} }; //c

a 1번 생성자가 3번 호출됩니다.

b 2번 생성자가 1번, 1번 생성자 2번

c 2번 생성자 2번, 1번 생성자 1번

 

malloc & new

Point* p = nullptr;

p = (Point*)malloc(sizeof(Point)); //a
p = new Point();                   //b
p = new Point(1,2);                //c

a 생성자가 호출되지 않습니다.

b 1번 생성자 호출

c 2번 생성자 호출

 malloc와 new는 둘 다 동적 메모리를 할당하지만, 가장 큰 차이는 생성자 호출 유무에 있습니다.

 

클래스가 객체를 멤버로 가질 때, 생성 소멸자의 호출 순서?

class Point {
private:
	int x, y;

public:
	Point()	{}
	~Point() {}
};

class Triangle
{
private:
	Point p1;
	Point p2;
	Point p3;

public:
	Triangle() {}
	~Triangle() {}
};

 Triangle 삼각형 클래스를 만들고 멤버로 Point를 가지게 하였습니다. 그럼 Triangle을 생성하면 누구의 생성자부터 불리고, 파괴 시 누구의 소멸자부터 불리게 될까요? 이에 대해 알아두어야 합니다. 

 간단하게 로그를 출력해서 알아봅시다.

class Point {
private:
	int x, y;

public:
	Point() { cout << "Point()" << endl; }
	~Point() { cout << "~Point()" << endl;	}
};

class Triangle
{
private:
	Point p1;
	Point p2;
	Point p3;

public:
	Triangle() { cout << "Triangle()" << endl; }
	~Triangle() { cout << "~Triangle()" << endl; }
};

출력결과

생성 시에는 내부의 멤버들부터 생성되고, 본인이 생성됩니다.

소멸 시에는 자신의 소멸자부터 호출되고 이후 멤버의 소멸자가 호출됩니다.

 알아두시면 필요할 경우가 분명 있습니다. 

 

배웁시다.

봐주셔서 감사합니다.

반응형

댓글