티스토리 뷰

IT/C, C++

[C/C++] 생성자 (Constructor)

주인장 진빼이

생성자(Constructor)란 ?

클래스의 객체가 생성되었을 때 객체를 초기화하는 목적으로 실행하는 함수이다.

함수와 동일하게 매개변수와 코드를 실행하는 영역을 가지고 있다. 하지만 반환값은 존재하지 않는다.

 

생성자는 멤버함수(메소드)이며, 보통 public 접근제한자를 사용하여 사용된다.

생성자와 반대되는 개념으로 객체가 소멸될 때 호출되는 소멸자가 존재한다.

 

생성자의 특징과 요약은 다음과 같다.

특징 요약
오버라이딩 불가능
오버로딩 가능
반환값 없음
함수 이름 클래스와 동일
호출 시점 해당 클래스의 객체가 생성될 때
기본 생성자 없을 시  매개변수 생성자가 없을 시 컴파일러에 의한 자동 생성

예제를 통해 특징에 대 자세히 알아보자

 

기본 생성자(Default-Constructor)와 매개변수 생성자(Parameterized-Constructor)

생성자는 2가지 형태를 가지고 있다.

매개변수가 없는 생성자를 기본 생성자라 하고 매개변수가 존재하는 생성자를 기본 생성자라고 한다.

 

왜 매개변수가 생성자가 필요할까 ?

  : 객체를 생성할 때 원하는 값으로 초기화하기 위한 편의성 때문이다. 객체가 가진 데이터를 무조건 0으로만 초기화해야하는 것은 아니기 때문이다. 

 

컴파일러가 기본 생성자를 만들어주는데 하나 이상의 매개변수 생성자가 정의되어 있다면 기본 생성자는 만들어주지 않으며, 컴파일 오류가 발생된다. 객체를 생성할 때 매개변수를 전달하면 매개변수와 일치하는 매개변수 생성자가 호출되고, 매개변수를 넘겨주지 않으면 기본 생성자가 호출된다.

 

생성자를 정의하는 방법

기본 생성자를 정의하는 방법은 클래스 이름을 적어주고 뒤에 함수꼴로 소괄호를 붙여주는 것이다.

아래처럼 "ClassName" 대신 클래스 타입 이름을 적어주면 된다.

ClassName()
{

}

 

매개변수 생성자를 정의하는 방법은 다음과 같다.

ClassName(int n1, int n2, int n3)
{

}

 

생성자는 여러개의 매개변수를 가질 수 있고, 오버로딩을 지원한다.

그러므로 다음과 같이 사용할 수 있다.

ClassName(int n1, int n2)
{
	// code
}

ClassName(int n1)
{
	// code
}

ClassName(double d1)
{
	// code
}

 

 

 

예제를 통해 호출 시점을 확인해보고 생성자에 대해 자세히 알아보자.

다음 예제는 GCC를 이용하여 작성되었다.

예제: 생성자의 호출 시점 확인하기

Date 타입의 변수를 선언하면 기본적으로 기본 생성자가 호출된다. 동적할당을 해도 마찬가지로 생성자가 호출된다.

클래스에서 객체가 인스턴스화(객체의 데이터가 메모리에 상주)되었을 때 생성자는 호출된다.

#include <iostream>

class Date
{
public:
	int year;
	int month;
	int day;
	Date()
	{
		std::cout << "[Constructor] Date()\n";
	};
};

int main()
{
	Date date;
	Date *pDate = new Date;
	return 0;
}

 

//==== output ====//
[Constructor] Date()
[Constructor] Date()

 

 

예제: 매개변수 생성자를 정의하여 객체 생성 후 출력값 확인하기

만약 기본 생성자를 사용했다면 일일히 멤버변수에 0씩 대입해야 했다. 하지만 매개변수 생성자를 사용하면

Date타입의 변수를 선언하고 즉시 원하는 값으로 초기화를 할 수 있기에  두마리의 토끼를 잡을 수 있다.

#include <iostream>

class Date
{
public:
	int year;
	int month;
	int day;

	Date(int year, int month, int day)
	{
		this->year = year;
		this->month = month;
		this->day = day;
		std::cout << "[Constructor] Date() - param: year, month, day\n";
	};

	void ShowData()
	{
		std::cout << "Year: " << year << "\n";
		std::cout << "month: " << month << "\n";
		std::cout << "day: " << day << "\n\n";
	}
};

int main()
{
	Date date(2020, 7, 4);
	date.ShowData();
	return 0;
}
//===== output =====//
[Constructor] Date() - param: year, month, day
Year: 2020
month: 7
day: 4

 

생성자를 호출하는 여러가지 방법

생성자는 객체가 호출 될 때 생성된다고 했다. 기본 생성자를 호출할 때 주의사항이 존재한다.

아래 4줄의 코드는 모두 매개변수 생성자를 호출한다.

하지만 초기화 하는데 조금 다를 수 있다. 자세한 사항은 C++11 균일한 초기화에 대해 알아보자.

Date date(2020, 7, 4);
Date date2 = {2020, 7, 4};
Date *date3 = new Date(2020, 7, 4);
Date date4{2020, 7, 4};

 

주의사항: 기본 생성자를 호출할 땐 괄호를 사용하지말자

자주 실수하는 것중에 기본 생성자를 호출하려고할 때, 소괄호를 뒤에 사용하는 것이다. // 소괄호: (, )

매개변수 생성자를 호출할 땐 소괄호를 사용하여 필요한 매개변수에 해당하는 값을 적어준다.

Date date(2020, 7, 4);

 

매개변수 생성자에서 매개변수 목록만 지우면 마치 기본 생성자를 호출할 수 있듯이 보인다.

Date date();

 

하지만 컴파일러는 이것이 기본 생성자의 호출인지

Date 타입의 데이터를 반환하는 date 이름을 가진 함수의 전방선언인지 구분을하지 못한다.

그러므로 기본 생성자를 호출 할 땐 구조체, 클래스 변수를 선언하듯이 적어주면 된다.

Date date;

 

 

예제: 매개변수 생성자가 존재하지 않을 때 컴파일러가 자동으로 만들어주는 기본 생성자

컴파일러는 기본 생성자를 포함하여 생성자가 아예 없다면 기본으로 생성자를 만들어준다.

하지만 생성자 코드 영역에 문자열을 출력한 것이 아니다. 그러므로 아무 내용도 보이지 않는다. (컴파일은 성공)

만약, 매개변수 생성자가 1개라도 존재한다면 컴파일러는 기본 생성자를 호출할 수 없어, 객체를 만들지 못하고 오류가 발생하게 된다.

#include <iostream>

class Date
{
public:
	int year;
	int month;
	int day;
};

int main()
{
	Date date;
	Date *pDate = new Date;
	return 0;
}
//==== output ====//

 

 

예제: 상속 관계에서의 생성자 호출 시점 확인하기

자식 객체를 생성하면 생성되기 전에 자식을 상속한 모든 부모 클래스의 객체가 만들어진다.

만들어진 부모들의 객체는 자식 객체가 소멸됨에 따라 역순 (Child -> Parent -> Grandfa)로 소멸된다.

 

객체가 만들어진다는 의미는 자식을 상속한 모든 부모 클래스의 기본 생성자가 호출된다는 의미이다.

그리고 생성자가 호출될 때(자식 객체가 만들어질 때)마다 계속 반복된다.

#include <iostream>

class Grandfa
{
public:
	int nn = 50;
	Grandfa()
	{
		printf("[Constructor] Grandfa! %p\n", this);
		// std::cout << "[Constructor] Grandfa!\n";

	}
	Grandfa(int n)
	{
		std::cout << "[Constructor] Grandfa param: " << n << "!\n";
	}
};

class Parent : public Grandfa
{
public:
	Parent()
	{
		std::cout << "[Constructor] Parent!\n";
	}
	Parent(int n)
	{
		std::cout << "[Constructor] Parent param: " << n << "!\n";
	}
};

class Child : public Parent
{
public:
	Child()
	{
		std::cout << "[Constructor] Child!\n\n";
	}
	Child(double d1)
	{
		printf("Instance of Grandfa(in): %p\n", &(Parent::nn));
		std::cout << "[Constructor] Child param: " << d1 << "!\n\n";
	}
};

int main()
{
	Child child(300);
 	return 0;
}
//==== output ====//
[Constructor] Grandfa! 0xbffa97d0
[Constructor] Parent!
Instance of Grandfa(in): 0xbffa97d0
[Constructor] Child param: 300!

 

범위 지정 연산자(::)를 이용하여 자식 객체가 만들어질 때 부모들의 객체가 가진 메모리에도 접근할 수 있다.

객체의 첫번째 멤버변수의 주소는 객체가 할당한 메모리의 시작 주소를 의미한다.

 

 

포인터 멤버 변수를 이용하여 만들어진 부모들의 객체로 접근할 수 있다.

아래 코드에서 Parent::nn으로 접근하고 있지만 결국에 nn의 주인은 Grandfa이므로

Grandfa의 메모리 주소가 출력된다. 그리고 멤버 변수를 이용하여 main() 함수 영역에서 값을 확인할 수도 있다.

(위 코드에서 Child 클래스 코드와 main() 함수 코드를 살짝 변경했다)

class Child : public Parent
{
public:
    Grandfa *pGrandfa_ = nullptr;
    Child()
    {
        std::cout << "[Constructor] Child!\n\n";
    }
    Child(double d1)
    {
        printf("Instance of Grandfa(in): %p\n", &(Parent::nn));
        std::cout << "[Constructor] Child param: " << d1 << "!\n\n";
        pGrandfa_ = (Grandfa*)&(Parent::nn);
    }
    void showInstanceAddr()
    {
        std::cout << "showInstanceAddr: " << pGrandfa_ << "\n";
    }

};

int main()
{
    Child child(300);
    child.showInstanceAddr();
    std::cout << child.pGrandfa_->nn;
    return 0;
}
//==== output ====//
[Constructor] Grandfa! 0xbff737c8
[Constructor] Parent!
Instance of Grandfa(in): 0xbff737c8
[Constructor] Child param: 300!

showInstanceAddr: 0xbff737c8
50

 객체가 처음으로 할당되는 주소들은 실행될 때마다 달라진다. (en.wikipedia.org/wiki/Address_space_layout_randomization)

 

 

마지막으로 각 클래스들에게 int타입의 멤버변수를 하나씩 선언하여

Grandfa는 50, Parent는 2, Child는 3 값들을 가지고 있을 때 메모리 구조를 확인해보았다.

Child 타입의 변수를 하나만 선언했음에도 불구하고 Grandfa, Parent, Child 메모리가 나열되어 있는 것을 볼 수 있다.

그리고 마지막(0x006FFAE8)에는 Little-Endian으로 기록된 Grandfa의 주소가 있다.

 

Child 타입의 변수를 선언함으로서 부모 객체가 자동으로 생성될 때 내부의 this는 모두 같은 곳(0x6FFADC)을 가르키고 있다.

 

 

 

생성자, 복잡해 보이지만 조금만 연습하면 쉽다.

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함