인스턴스 (instance)
인스턴스는 추상화 개념이 실제 구현된 것을 말합니다.
- 추상화 개념은 우리가 추상화 하여 설계한 클래스를 의미
- 실제 구현은 메모리 상에 실존 하는 데이터를 의미
Person 클래스는 사람을 추상화 하여 설계한 클래스입니다.
int main(int argc, char** argv)
{
Person tom,alex;
}
Person 클래스로 tom 과 alex 라는 변수를 만들었습니다.
tom 과 alex 는 각각 자신만의 메모리 공간을 갖습니다. 즉, Person 의 Instance 입니다.
new / delete
C++ 에서는 인스턴스가 보다 잘 표현되도록 특별한 키워드를 제공합니다.
new 는 동적 메모리 할당을 대신하며, delete 는 메모리 해제를 대신합니다.
Person* tom = new Person;
delete tom;
new 와 delete 는 클래스가 아닌 기본 자료형이나 구조체에도 사용될 수 있습니다.
- new 와 delete 는 C++ 키워드이기에, 별도의 헤더를 포함하지 않아도 사용할 수 있습니다.
Person* tom = new Person[4];
delete [] tom;
배열에 대해 선언할 경우, 위와 같은 형태로 사용할 수 있습니다.
malloc 과 free 그리고 new 와 delete 를 혼용하여 사용할 경우 문제가 발생할 수 있습니다. 주의가 필요합니다.
- malloc 과 free 는 단순히 요청한 크기 만큼의 메모리 공간을 돌려줍니다.
- new 와 delete 는 생성자와 소멸자를 호출하며, 클래스 구현에 필요한 부가적인 메모리를 추가로 할당합니다.
- 가상 함수 테이블 설명 문서를 참조해 주세요.
다차원 배열에 대한 new / delete
C++에서 new delete 를 이용해 다차원 배열을 직접적으로 선언하거나 제거하는 방법은 없습니다. 따라서, 2차원 이상의 배열을 사용하기에는 다소 불편할 수 있습니다.
먼저 포인터 배열을 만든 후 1차원 배열을 연결하는 방법이 있습니다.
- 이 경우 연속된 하나의 메모리라 보장 할 수 없습니다.
- 포인터 연산으로 배열을 탐색하는 경우 안전을 보장할 수 없습니다.
#include <iostream>
int main(int argc, char** argv)
{
// array[20][20]
int** array = new int*[20];
for (int i = 0; i < 20; i++)
{
array[i] = new int[20];
}
array[2][3] = 3;
printf("%d", array[2][3]);
return 0;
}
다른 방법으로는 큰 1차원 배열을 선언하고, 산술적으로 index 값을 계산할 수 있습니다.
- 연산자 오버로딩 등을 이용해서 처리하면 조금 더 편리하게 사용할 수 있습니다.
#include <iostream>
#define ArrayPos(x,y,ySize) (x*ySize)+y
int main(int argc, char** argv)
{
// array[20][20]
int* array = new int[20*20];
array[ArrayPos(2,3,20)] = 3;
printf("%d", array[ArrayPos(2,3,20)]);
return 0;
}
또 다른 방법으로는 역시 마찬가지로 나중에 다룰 template class 를 활용할 수 있습니다.
array 나 vector 등의 Standard template library 를 활용하면 부가적인 코드량을 줄일 수 있습니다.
#include <iostream>
#include <array>
int main(int argc, char** argv)
{
// array[20][20]
std::array<std::array<int, 20>, 20> array;
array[2][3] = 3;
printf("%d", array[2][3]);
return 0;
}
new / delete testing code
new 와 delete 의 배열 선언시 발생하는 일을 추적하기 위한 간단한 예제 코드입니다.
실제 메모리가 할당되고 제거된 것을 직접 확인하려면 별도의 개발도구들을 사용할 필요가 있으므로, 여기서는 '생성자' 와 '소멸자' 를 이용해 추적합니다.
기본적인 정의대로 객체의 생성시와 소멸시 메모리가 할당되거나 소멸되고, 이때 반드시 생성자와 소멸자가 호출이 보장된다고 가정합시다.
- 즉, 별도의 외부 원인이 개입되지 않는다고 가정한다는 전제를 둡니다.
#include <iostream>
class TestingClass
{
private:
int val;
public:
TestingClass()
{
printf("Constructor : %x\n",this);
}
~TestingClass()
{
printf("Destructor : %x\n", this);
}
};
int main(int argc, char** argv)
{
printf("[Case1]\n");
printf("new TestingClass[5] \n");
TestingClass* Case1 = new TestingClass[5];
printf("\ndelete[] testingClass \n");
delete[] Case1;
printf("\n[Case2]\n");
printf("new TestingClass*[2] \n");
TestingClass** Case2 = new TestingClass*[2];
for (int i = 0; i < 2; i++)
{
Case2[i] = new TestingClass[2];
}
printf("\ndelete[] testingClass \n");
delete[] Case2;
}
new TestingClass[5]
Constructor : 129a79c
Constructor : 129a7a0
Constructor : 129a7a4
Constructor : 129a7a8
Constructor : 129a7ac
delete[] testingClass
Destructor : 129a7ac
Destructor : 129a7a8
Destructor : 129a7a4
Destructor : 129a7a0
Destructor : 129a79c
new TestingClass*[2]
Constructor : 1298f34
Constructor : 1298f38
Constructor : 129a79c
Constructor : 129a7a0
delete[] testingClass
결과에 표기되는 값은 %x로 출력한 this 포인터의 값으로, 해당 객체의 주소 값입니다.
Case1는 1차원 배열입니다.
- 각 객체는 4byte 간격으로 생성되었고, 정확히 5번 호출되었습니다.
- delete[] testingClass 를 호출하면, 생성될때의 반대의 순서로 소멸자가 호출됩니다.
Case2는 위에서 언급한 방법을 통해 만든 2차원 배열입니다.
- Case2[i] = new TestingClass[2];과 같은 하위 1차 배열은 연속적입니다.
- 하나의 배열이 아닌 여러 개의 배열을 묶어놓은 것에 불과합니다.
- delete[]는 하위 배열을 해제하지는 않습니다.
TestingClass** Case2 = new TestingClass*[2];
위 코드는 'TestingClass 포인터' 배열을 선언합니다.
Case2[i] = new TestingClass[2];
'TestingClass' 배열을 'TestingClass 포인터' 배열인 Case2에 담습니다.
- new 의 리턴 값은 할당된 배열의 첫 주소입니다.
- 배열의 이름은 해당 배열의 첫 주소값을 가르키는 포인터이기도 합니다.
delete [] Case2;
'TestingClass 포인터' 배열을 해제 합니다.
- 'TestingClass 포인터' 배열이 가지고 있는 값인 'TestingClass' 배열의 주소들에 대해 해제가 이루어지지는 않습니다.
- 명시적으로 하위 배열부터 직접 해제해야만 합니다.
따라서 Case2는 TestingClass 의 어떠한 소멸자도 호출되지 않았으며, 'TestingClass 포인터' 배열은 해제되었지만, 하위의 'TestingClass' 배열 메모리는 해제 되지 않았습니다.
- 'TestingClass 포인터' 는 클래스 객체가 아닌 숫자 값(주소)이며, 소멸자가 없습니다.