본문 바로가기

C++

C++의 객체 배열, 오브젝트 배열 사용법과 원리

[ How C++ initializes object arrays ]

● C++ 객체 배열

C++에서 처음에 배열을 사용할 때, 언제 생성자가 호출되어 초기화가 되는 지 헷갈린다.

그래서 객체를 다루는 배열에 대하여 정리해보겠다.


● 기본적인 배열 선언 및 초기화 방법

참고로 기본적으로 배열을 선언하는 법은 예시와 함께 아래와 같다.

( pseudo code 이다. 편의상 같은 변수명을 사용했다. Point라는 클래스를 정의한 경우라고 하자.)

자료형 변수명[]={ , , , }
int arr[]={1,2,3};
int arr[3];
int arr[3]={1,2,3};

Point pointArr[]={Point(), Point(), Point()};
Point pointArr[3];
Point pointArr[3]={Point(), Point(), Point()};

( int arr[3]의 경우 초기화가 되지 않고 쓰레기값을 가리킨다. )


● 객체 배열이 초기화 되는 원리

여기서 Point 배열은 어떻게 초기화될까?

우선 객체의 배열은 무조건 선언과 함께 초기화된다고 봐도 될 것 같다. 

( 제 생각임. 아닐 수도 있긴한데, 아직 안 그런 경우 못 찾았음. 객체의 포인터의 배열이면 객체가 초기화 안 될 수도 있음. )


Point pointArr[]={Point(), Point(), Point()};

위와 같은 코드는 당연히 내가 초기화를 해서 집어넣으니까 당연히 초기화되고, 생성자가 호출되는 것이 눈에 보인다.


하지만

Point pointArr[3];

의 경우 int 형 array 선언과 달리 ( int 형 array는 초기화되지 않음. )

선언과 함께 default constructor가 호출된다.

default constructor을 정의하지 않으면, 컴파일이 되지 않는다.

혹은 default constructor 외에

 Point(int x=0, int y=0)  이런 식으로 인자를 받지 않아도 정상적으로 생성자가 호출될 수 있게 default arguments 설정을 해주어도 된다.


Point * p1 = new Point[5];

동적으로 객체 배열을 선언하는 경우에도 default constructor을 이용해 모두 초기화된다.

위의 코드가 무엇을 의미하냐면,

Point형 객체에 대한 5개를 사용할 수 있는 메모리 공간을 동적으로 확보하고,

각 공간에 default constructor로 객체를 생성한다.

이후 첫 번째 객체의 주소를 p1 pointer에 저장한다.


● 객체의 포인터 배열이 선언되는 원리

그렇다면 배열을 동적으로 할당하고, 그 안에 정적으로 생성하는 객체를 담는 것이 아니라,

배열은 정적이되 객체를 동적으로 할당하면 어떻게 될까.


즉 아래와 같이 말이다.

Point * parr[5];
for (int i = 0 ; i < 5; i++)
{
	parr[i] = new Point(i * 10, i * 10);
}

이 경우 첫 번째 줄에서는 아무런 객체도 생성되지 않는다.

5칸 짜리 배열 내의 pointer들이 어떠한 쓰레기값으로 초기화될 뿐이다(?)

( 사실 이 부분은 뭘로 초기화되는 지 아직 잘 모름겠음. NULL 포인터의 경우 00000으로 출력되는데, 지금은 00000으론 출력이 안 되는 걸 보아, NULL 포인터로 초기화되는 건 아닌 것 같은데 )


어쨌든, 첫 줄에서는 Point에 대한 생성자는 호출되지않고, 객체도 만들어지지 않고, 그냥 pointer에 대한 배열을 선언한 것 뿐이다.


그리고 그 뒤에 for문에서 Point 객체를 생성하는데, 이 때, parr의 각 칸들은 pointer들이기때문에,  Point(arg1, arg2)  형식이 아니라 동적으로 객체를 생성해주는  new Point(arg1, arg2)  형식으로 생성자를 호출해주어야한다.


아까부터 default constructor 얘기만 꺼냈는데, 인자를 전달할 수는 없는가?

즉. C++에서 객체에 대한 배열은 선언과 함께 초기화되는데, 이 때 초깃값을 default constructor로 생성한 객체가 아니라, 어떠한 인자를 전달받은 값으로 초기화할 수는 없을까?


방법이 있긴하다. 하지만 내가 아는 선에서는 각 생성자마다 하나의 인자만 전달 받을 수 있다.

예를 들어    Point arr[5]{10,20,30};   에서

0번째 칸은 10을 인자로 받은 constructor, 

1번째 칸은 20을 인자로 받은 constructor,

2번째 칸은 30을 인자로 받은 constructor,

3,4 번째 칸은 default constructor 혹은 default argument가 모두 설정된 constructor을 이용해 생성되고 초깃값을 부여받는 것이다.


● 정리

1. C++ 에서 객체에 대한 배열은 선언과 동시에 항상 default constructor 혹은 default argument에 의해 인자 없이도 호출 가능한 생성자로 초기화가 된다.


2. 예외적으로   Point arr[5]{10,20,30};  등의 표현을 통해 인자를 하나씩 전달할 수 있기는 하다


3. 객체의 포인터에 대한 배열은 선언시 객체를 생성하지는 않는다.

하나하나 객체를 new로 생성하여 할당해주면 된다.


● 예시 소스 및 출력

위의 내용을 확인해볼 수 있는 코드


#include <iostream>
using namespace std;

class Point
{
//편의상 public으로 선언했음.
public:
	int x, y;
public:

	Point(int x=0, int y=0)
	{
		cout << "( "< x = x;
		this->y = y;
	}

	Point(const Point& p)
	{
		this->x = p.x;
		this->y = p.y;
		cout << "복사생성자!" << endl;
	}
	
};
int main()
{
	Point	arr[5]{10,20,30};
	cout << "=======================" << endl;
	Point * p1 = new Point[5];

	cout << "=======================" << endl;
	Point * p2 = new Point[5]{ 10, 20, 30, 40 };
	cout << "=======================" << endl;
	//객체의 포인터에 대한 배열
	Point * parr[5];
	for (int i = 0 ; i < 5; i++)
	{
		parr[i] = new Point(i * 10, i * 10);
	}
	
}