C++ - 객체 소멸 순서, 객체 생성(정적, 동적 할당-malloc, new)
내장형 하드웨어/C++ / 2011. 11. 15. 12:14
- class 포인터라는 것은 구조체 포인터와 같다.
#ifdef DEBUG
cout << "car class test()" << endl;
#endif
→ g++ -o main main.cpp -DDEBUG
→ 디버거를 켜고 끔으로서 컴파일 단계에서 옵션으로 동작 구분이 가능하다.
#if DEBUG
cout << "car class test()" << endl;
#endif
→ g++ -o main main.cpp -DDEBUG=0
→ 위와 같은 컴파일기법도 있다.
- 구성(composition)
→ 클래스를 상속 안받고 내부에 선언해서 사용하는 것
※ 클래스 내 변수 초기화는 안된다.(메모리에 존재하지 않는다. 구조체와 같다.)
→ 당연히 둘다 에러이다. 구조체와 마찬가지로 class도 메모리에 존재하지 않으므로 클래스 내 변수 초기화는 안된다.
→ 따라서 생성자 내에서 초기화 해야 한다.
→ 헷갈릴 때는 struct를 생각해 본다.
→ myAudio = audio::audio("aa"); 라고 생성자에 선언하면 오류가 발생한다.(세그멘테이션 오류)
→
→ 위의 소스는 생성자 강제 호출(audio::audio("aa"))로 객체에 객체가 대입된다.
→ 즉, audio::audio("aa")는 대입을 위해 존재하고 소멸한다.(free()가 존재)
→ 그런데 MyAudio는 audio::audio("aa")가 소멸시킨 주소를 가리키고 있으므로 실행하면 세그멘테이션 오류가 발생한다.
→ 이를 해결하려면
→ K5() // 생성자
:car(12, "KIA"), myAudio("aa") // 다중 상속 받았을 때 생성자 구동
{
→ 와 같은 방법을 사용한다.
- 자동 캐스팅
→ 자동 캐스팅은 상속된 타입 속성에 따라 가능한 것과 불가능 한 것이 구분된다.
→ 만약 A 클래스와 B 클래스가 있고 B 클래스가 A 클래스를 상속 받으면
→ 위와 같은 자동 캐스팅은 남발하면 문제가 발생하게 된다.
→ 생성자와 소멸자를 클래스의 밖으로 빼서 사용하는 경우
→ 밖으로 뺀 소멸자의 앞에다가 스코프 연산자(::)를 사용해 주면 사용이 가능하다.
※ C 복습
→ 함수의 정의는 끝에 세미콜론(;)이 없다.(중괄호로 끝을 표시) 변수의 정의에는 세미콜론(;)이 끝에 있다.
→ 따라서 main(), 생성자, 소멸자 등의 함수에는 세미콜론을 붙이지 않는다.
→ 반대로 struct, class 등에는 세미콜론을 붙인다.
→ 변수의 유효기간
→ for문안에서 새로 선언한 변수는 해당 for문이 끝나면 사라진다.
→ 확인해 보면
→ 다음으로 전역, 지역, 함수의 객체 생성 및 소멸의 순서를 확인해 본다.
→ 예제
→ 소멸 순서 : 지역 -> 함수 -> 전역
→ JAVA와 C++의 차이(일반 자료형과 객체)
→ class A가 존재하고 20byte라고 가정할 때
#ifdef DEBUG
cout << "car class test()" << endl;
#endif
→ g++ -o main main.cpp -DDEBUG
→ 디버거를 켜고 끔으로서 컴파일 단계에서 옵션으로 동작 구분이 가능하다.
#if DEBUG
cout << "car class test()" << endl;
#endif
→ g++ -o main main.cpp -DDEBUG=0
→ 위와 같은 컴파일기법도 있다.
- 구성(composition)
→ 클래스를 상속 안받고 내부에 선언해서 사용하는 것
※ 클래스 내 변수 초기화는 안된다.(메모리에 존재하지 않는다. 구조체와 같다.)
class car { int K = 100; } |
struct car { int K = 100; } |
→ 따라서 생성자 내에서 초기화 해야 한다.
→ 헷갈릴 때는 struct를 생각해 본다.
#if 1
#include<iostream.h>
#include<stdlib.h>
#else
#include<stdio.h>
#endif
void test();
// 자동차 클래스
class car
{
// 클래스 내에서만 사용할 수 있다.
private:
unsigned int uiNumber; // 자동차 일련 번호
unsigned char* ucpVendor; // 제조사
// 생성자 - 자동 호출 된다.(반환형이 없는 특수한 경우)
// 객체를 생성할 때 만들어 진다.
// 생성자는 여러개를 만들 수 있으나 소멸자는 하나만 만들 수 있다.
car() // 인자가 없는 생성자를 디폴트 생성자라 한다.
{
uiNumber = 0;
ucpVendor = 0;
#ifdef DEBUG
cout << "Car class 생성자 호출" << endl;
#endif
}
car(unsigned int uiNumber) // 자동차 일련번호
{
ucpVendor = 0;
(*this).uiNumber = uiNumber;
#ifdef DEBUG
//cout << this->uiNumber << " Car class 생성자 호출" << endl;
#endif
}
// 전체가 사용할 수 있다.
public:
int C;
void test(char)
{
#ifdef DEBUG
cout << "car class test()" << endl;
#endif
}
// 상속 받은 클래스만 사용할 수 있다.
protected:
unsigned int uiSpeed;
// 제조사
car(unsigned int uiNumber, char *cpVendor)
{
unsigned int uiLen; // 길이를 받기 위해
uiLen = strlen(cpVendor);
ucpVendor = (unsigned char*)malloc(uiLen+1); // NULL을 위해 +1
strcpy((char *)ucpVendor,cpVendor);
(*this).uiNumber = uiNumber;
//cout << this->uiNumber << cpVendor<< " Car class 생성자 호출" << endl;
}
~car() // 소멸자 - 앞에 틸드(~)를 붙인다. main함수가 종료되기 전에 호출된다.
{
if(0 != ucpVendor)
{
free(ucpVendor);
}
//cout << uiNumber << " CAR class 소멸자 호출" << endl;
}
};
// 오디오 클래스
class audio
{
private:
char *cpMaker;
public:
int A;
audio() // 생성자
{
cpMaker = 0;
//cout << "audio class" << endl;
}
// 인자의 타입만 쓸수도 있다.(C와의 차이점)
audio(char *cpMaker)
{
unsigned int uiLen; // 길이를 받기 위해
uiLen = strlen(cpMaker);
this->cpMaker = (char*)malloc(uiLen+1); // NULL을 위해 +1
strcpy((*this).cpMaker, cpMaker);
#ifdef DEBUG
cout << this->cpMaker << " Audio class 생성자 호출" << endl;
#endif
}
~audio() // 소멸자
{
if(0 != cpMaker)
{
free(cpMaker);
}
#ifdef DEBUG
cout << this->cpMaker << " Audio class 소멸자 호출" << endl;
#endif
}
void test(float)
{
#ifdef DEBUG
cout << "audio class test()" << endl;
#endif
}
};
// public을 붙여주지 않으면 기본적으로 상속은 private이 된다.
// 따라서 main()에서 호출하면 접근속성 제한이 되어 사용할 수 없다.
// 즉, public car로 사용하므로써 main에서 int C; 에 접근 가능하다.
class K5:public car
{
private:
// 구성(composition), 상속이 아니므로 변수로 사용할 수 있다.
audio myAudio;
public:
int K;
K5() // 생성자
:car(1234, "KIA"), myAudio("JBL") // 다중 상속 받았을 때 생성자 구동
{
// 생성자 강제 호출로 객체에 객체가 대입된다.
// 즉, audio::audio("a")는 대입을 위해 존재하고 소멸한다.(free)
// 그런데 MyAudio는 audio::audio("a")가 소멸시킨 주소를 가리키고 있으므로
// 실행하면 세그멘테이션 오류가 발생한다.
// MyAudio = audio::audio("a"); - 오류코드
#ifdef DEBUG
cout << "(생성자)K5가 생성되었습니다." << endl;
#endif
uiSpeed = 0;
//uiNumber = 7; // private이므로 에러
}
~K5(void) // 소멸자
{
#ifdef DEBUG
cout << "(소멸자)K5가 소멸되었습니다." << endl;
#endif
}
void speedUp(void)
{
uiSpeed = uiSpeed+10;
if(100<uiSpeed)
{
uiSpeed = 100;
}
#ifdef DEBUG
cout << "지금 속도는 " << uiSpeed << "Km입니다." << endl;
#endif
return ;
}
void speedDown(void)
{
if(0==uiSpeed)
{
uiSpeed = 0;
}
else
{
uiSpeed = uiSpeed-10;
}
#ifdef DEBUG
cout << "지금 속도는 " << uiSpeed << "Km입니다." << endl;
#endif
}
void test(int )
{
#ifdef DEBUG
cout << "K5 class test()" << endl;
#endif
}
};
int main()
{
K5 myCar;
return 0;
}
→ #include<iostream.h>
#include<stdlib.h>
#else
#include<stdio.h>
#endif
void test();
// 자동차 클래스
class car
{
// 클래스 내에서만 사용할 수 있다.
private:
unsigned int uiNumber; // 자동차 일련 번호
unsigned char* ucpVendor; // 제조사
// 생성자 - 자동 호출 된다.(반환형이 없는 특수한 경우)
// 객체를 생성할 때 만들어 진다.
// 생성자는 여러개를 만들 수 있으나 소멸자는 하나만 만들 수 있다.
car() // 인자가 없는 생성자를 디폴트 생성자라 한다.
{
uiNumber = 0;
ucpVendor = 0;
#ifdef DEBUG
cout << "Car class 생성자 호출" << endl;
#endif
}
car(unsigned int uiNumber) // 자동차 일련번호
{
ucpVendor = 0;
(*this).uiNumber = uiNumber;
#ifdef DEBUG
//cout << this->uiNumber << " Car class 생성자 호출" << endl;
#endif
}
// 전체가 사용할 수 있다.
public:
int C;
void test(char)
{
#ifdef DEBUG
cout << "car class test()" << endl;
#endif
}
// 상속 받은 클래스만 사용할 수 있다.
protected:
unsigned int uiSpeed;
// 제조사
car(unsigned int uiNumber, char *cpVendor)
{
unsigned int uiLen; // 길이를 받기 위해
uiLen = strlen(cpVendor);
ucpVendor = (unsigned char*)malloc(uiLen+1); // NULL을 위해 +1
strcpy((char *)ucpVendor,cpVendor);
(*this).uiNumber = uiNumber;
//cout << this->uiNumber << cpVendor<< " Car class 생성자 호출" << endl;
}
~car() // 소멸자 - 앞에 틸드(~)를 붙인다. main함수가 종료되기 전에 호출된다.
{
if(0 != ucpVendor)
{
free(ucpVendor);
}
//cout << uiNumber << " CAR class 소멸자 호출" << endl;
}
};
// 오디오 클래스
class audio
{
private:
char *cpMaker;
public:
int A;
audio() // 생성자
{
cpMaker = 0;
//cout << "audio class" << endl;
}
// 인자의 타입만 쓸수도 있다.(C와의 차이점)
audio(char *cpMaker)
{
unsigned int uiLen; // 길이를 받기 위해
uiLen = strlen(cpMaker);
this->cpMaker = (char*)malloc(uiLen+1); // NULL을 위해 +1
strcpy((*this).cpMaker, cpMaker);
#ifdef DEBUG
cout << this->cpMaker << " Audio class 생성자 호출" << endl;
#endif
}
~audio() // 소멸자
{
if(0 != cpMaker)
{
free(cpMaker);
}
#ifdef DEBUG
cout << this->cpMaker << " Audio class 소멸자 호출" << endl;
#endif
}
void test(float)
{
#ifdef DEBUG
cout << "audio class test()" << endl;
#endif
}
};
// public을 붙여주지 않으면 기본적으로 상속은 private이 된다.
// 따라서 main()에서 호출하면 접근속성 제한이 되어 사용할 수 없다.
// 즉, public car로 사용하므로써 main에서 int C; 에 접근 가능하다.
class K5:public car
{
private:
// 구성(composition), 상속이 아니므로 변수로 사용할 수 있다.
audio myAudio;
public:
int K;
K5() // 생성자
:car(1234, "KIA"), myAudio("JBL") // 다중 상속 받았을 때 생성자 구동
{
// 생성자 강제 호출로 객체에 객체가 대입된다.
// 즉, audio::audio("a")는 대입을 위해 존재하고 소멸한다.(free)
// 그런데 MyAudio는 audio::audio("a")가 소멸시킨 주소를 가리키고 있으므로
// 실행하면 세그멘테이션 오류가 발생한다.
// MyAudio = audio::audio("a"); - 오류코드
#ifdef DEBUG
cout << "(생성자)K5가 생성되었습니다." << endl;
#endif
uiSpeed = 0;
//uiNumber = 7; // private이므로 에러
}
~K5(void) // 소멸자
{
#ifdef DEBUG
cout << "(소멸자)K5가 소멸되었습니다." << endl;
#endif
}
void speedUp(void)
{
uiSpeed = uiSpeed+10;
if(100<uiSpeed)
{
uiSpeed = 100;
}
#ifdef DEBUG
cout << "지금 속도는 " << uiSpeed << "Km입니다." << endl;
#endif
return ;
}
void speedDown(void)
{
if(0==uiSpeed)
{
uiSpeed = 0;
}
else
{
uiSpeed = uiSpeed-10;
}
#ifdef DEBUG
cout << "지금 속도는 " << uiSpeed << "Km입니다." << endl;
#endif
}
void test(int )
{
#ifdef DEBUG
cout << "K5 class test()" << endl;
#endif
}
};
int main()
{
K5 myCar;
return 0;
}
→ myAudio = audio::audio("aa"); 라고 생성자에 선언하면 오류가 발생한다.(세그멘테이션 오류)
→
→ 위의 소스는 생성자 강제 호출(audio::audio("aa"))로 객체에 객체가 대입된다.
→ 즉, audio::audio("aa")는 대입을 위해 존재하고 소멸한다.(free()가 존재)
→ 그런데 MyAudio는 audio::audio("aa")가 소멸시킨 주소를 가리키고 있으므로 실행하면 세그멘테이션 오류가 발생한다.
→ 이를 해결하려면
→ K5() // 생성자
:car(12, "KIA"), myAudio("aa") // 다중 상속 받았을 때 생성자 구동
{
→ 와 같은 방법을 사용한다.
- 자동 캐스팅
→ 자동 캐스팅은 상속된 타입 속성에 따라 가능한 것과 불가능 한 것이 구분된다.
→ 만약 A 클래스와 B 클래스가 있고 B 클래스가 A 클래스를 상속 받으면
A AA;
A *AP;
B BB;
B *BP;
라고 선언했을 경우
//////////////////////
AP = &AA;
BP = &BB; 의 경우는 문제가 없다.
//////////////////////
BP = &AA;
AP = &BB; 의 경우에는 문제가 생긴다. AA의 경우는 A클래스 타입만 가지고 있고 BB의 경우는 A와 B타입을 모두 가지므로
BP = &AA;는 불가능 하고 AP = &BB; 의 경우는 가능하게 된다.
A *AP;
B BB;
B *BP;
라고 선언했을 경우
//////////////////////
AP = &AA;
BP = &BB; 의 경우는 문제가 없다.
//////////////////////
BP = &AA;
AP = &BB; 의 경우에는 문제가 생긴다. AA의 경우는 A클래스 타입만 가지고 있고 BB의 경우는 A와 B타입을 모두 가지므로
BP = &AA;는 불가능 하고 AP = &BB; 의 경우는 가능하게 된다.
→ 위와 같은 자동 캐스팅은 남발하면 문제가 발생하게 된다.
B BB[10];
A *AP;
AP = BB;
AP = AP+1; // 만약에 A타입과 B타입의 크기가 다르다면 잘못된 곳을 가리키게 되므로 위험한 에러가 발생한다.
→ 따라서 사용에 주의가 필요하다.A *AP;
AP = BB;
AP = AP+1; // 만약에 A타입과 B타입의 크기가 다르다면 잘못된 곳을 가리키게 되므로 위험한 에러가 발생한다.
→ 생성자와 소멸자를 클래스의 밖으로 빼서 사용하는 경우
// 오디오 클래스
class audio
{
private:
char *cpMaker;
public:
int A;
audio();
audio(char *cpMaker);
~audio();
void test(float)
{
#ifdef DEBUG
cout << "audio class test()" << endl;
#endif
}
};
audio::audio() // 생성자
{
cpMaker = 0;
//cout << "audio class" << endl;
}
// 인자의 타입만 쓸수도 있다.(C와의 차이점)
audio::audio(char *cpMaker)
{
unsigned int uiLen; // 길이를 받기 위해
uiLen = strlen(cpMaker);
this->cpMaker = (char*)malloc(uiLen+1); // NULL을 위해 +1
strcpy((*this).cpMaker, cpMaker);
#ifdef DEBUG
cout << this->cpMaker << " Audio class 생성자 호출" << endl;
#endif
}
audio::~audio() // 소멸자
{
if(0 != cpMaker)
{
free(cpMaker);
}
#ifdef DEBUG
cout << this->cpMaker << " Audio class 소멸자 호출" << endl;
#endif
}
→ 위와 같이 생성자와 소멸자를 클래스의 밖으로 빼고 클래스 내에 선언해 준 다음class audio
{
private:
char *cpMaker;
public:
int A;
audio();
audio(char *cpMaker);
~audio();
void test(float)
{
#ifdef DEBUG
cout << "audio class test()" << endl;
#endif
}
};
audio::audio() // 생성자
{
cpMaker = 0;
//cout << "audio class" << endl;
}
// 인자의 타입만 쓸수도 있다.(C와의 차이점)
audio::audio(char *cpMaker)
{
unsigned int uiLen; // 길이를 받기 위해
uiLen = strlen(cpMaker);
this->cpMaker = (char*)malloc(uiLen+1); // NULL을 위해 +1
strcpy((*this).cpMaker, cpMaker);
#ifdef DEBUG
cout << this->cpMaker << " Audio class 생성자 호출" << endl;
#endif
}
audio::~audio() // 소멸자
{
if(0 != cpMaker)
{
free(cpMaker);
}
#ifdef DEBUG
cout << this->cpMaker << " Audio class 소멸자 호출" << endl;
#endif
}
→ 밖으로 뺀 소멸자의 앞에다가 스코프 연산자(::)를 사용해 주면 사용이 가능하다.
※ C 복습
→ 함수의 정의는 끝에 세미콜론(;)이 없다.(중괄호로 끝을 표시) 변수의 정의에는 세미콜론(;)이 끝에 있다.
→ 따라서 main(), 생성자, 소멸자 등의 함수에는 세미콜론을 붙이지 않는다.
→ 반대로 struct, class 등에는 세미콜론을 붙인다.
→ 변수의 유효기간
→ for문안에서 새로 선언한 변수는 해당 for문이 끝나면 사라진다.
→ 확인해 보면
// for문안에서의 객체 생성은 비효율적이다.
// 생성되었다 삭제되었다를 반복하기 때문.
// 따라서 객체를 미리 만들어서 값을 집어 넣는 쪽이 효율적이다.
for(int iCnt=0;5>iCnt;++iCnt)
{
A temp(iCnt);
}
// for문안에 선언한 iCnt는 for문이 끝나면 stack에서 사라진다.
// 자주 사용하지 않을 때는 이쪽이 코드가 심플하고 편리하나
// 자주 사용한다면 그냥 위에 선언하고 사용하는 쪽이 낫다.
for(iCnt=0;5>iCnt;++iCnt) - 에러코드
{
A test(iCnt);
}
// 생성되었다 삭제되었다를 반복하기 때문.
// 따라서 객체를 미리 만들어서 값을 집어 넣는 쪽이 효율적이다.
for(int iCnt=0;5>iCnt;++iCnt)
{
A temp(iCnt);
}
// for문안에 선언한 iCnt는 for문이 끝나면 stack에서 사라진다.
// 자주 사용하지 않을 때는 이쪽이 코드가 심플하고 편리하나
// 자주 사용한다면 그냥 위에 선언하고 사용하는 쪽이 낫다.
for(iCnt=0;5>iCnt;++iCnt) - 에러코드
{
A test(iCnt);
}
→ 다음으로 전역, 지역, 함수의 객체 생성 및 소멸의 순서를 확인해 본다.
→ 예제
#include<iostream.h>
class A
{
public:
int i;
int j;
A();
A(int i);
A(int i, int j);
~A();
};
A::A()
{
cout << "A class 생성자 호출" << endl;
}
A::A(int i)
{
(*this).i = i;
cout << this->i << " : A class 생성자 호출" << endl;
}
A::A(int i, int j)
{
(*this).i = i;
(*this).j = j;
cout << this->i << " : A(int, int) class 생성자 호출" << endl;
cout << this->j << " : A(int, int) class 생성자 호출" << endl;
}
A::~A()
{
cout << this->i << " : A class 소멸자 호출" << endl;
}
void test()
{
// 처음 호출 될 때 객체를 호출한다.
// static을 사용하면 처음에 한번만 생성되며 이것은 객체에도 유효하다.
static A OBJ3(1000);
}
// main 함수 보다 전역이 먼저 실행됨을 결과로 확인할 수 있다.
A OBJ(1);
int main()
{
// 생성 순서 : 전역변수 -> 지역변수 or 함수
// 소멸 순서 : 지역 -> 함수 -> 전역
A OBJ2(2);
// static으로 객체를 선언했으므로 한번만 객체가 만들어 진다.
// 포인터를 사용할 때 문제가 있으므로 생성과 소멸 순서를
// 제대로 알고 있어야 한다.
test();
test();
test();
test();
test();
return 0;
}
→ 생성 순서 : 전역 -> 지역 or 함수class A
{
public:
int i;
int j;
A();
A(int i);
A(int i, int j);
~A();
};
A::A()
{
cout << "A class 생성자 호출" << endl;
}
A::A(int i)
{
(*this).i = i;
cout << this->i << " : A class 생성자 호출" << endl;
}
A::A(int i, int j)
{
(*this).i = i;
(*this).j = j;
cout << this->i << " : A(int, int) class 생성자 호출" << endl;
cout << this->j << " : A(int, int) class 생성자 호출" << endl;
}
A::~A()
{
cout << this->i << " : A class 소멸자 호출" << endl;
}
void test()
{
// 처음 호출 될 때 객체를 호출한다.
// static을 사용하면 처음에 한번만 생성되며 이것은 객체에도 유효하다.
static A OBJ3(1000);
}
// main 함수 보다 전역이 먼저 실행됨을 결과로 확인할 수 있다.
A OBJ(1);
int main()
{
// 생성 순서 : 전역변수 -> 지역변수 or 함수
// 소멸 순서 : 지역 -> 함수 -> 전역
A OBJ2(2);
// static으로 객체를 선언했으므로 한번만 객체가 만들어 진다.
// 포인터를 사용할 때 문제가 있으므로 생성과 소멸 순서를
// 제대로 알고 있어야 한다.
test();
test();
test();
test();
test();
return 0;
}
→ 소멸 순서 : 지역 -> 함수 -> 전역
→ JAVA와 C++의 차이(일반 자료형과 객체)
→ class A가 존재하고 20byte라고 가정할 때
JAVA | C++ |
int A; Z obj;( 포인터이다. 4byte) → java는 바로 생성하지 않는다.(모두 동적할당.) → 동적할당이므로 heap 영역 obj = new z; → obj는 객체 참조(reference) 변수 obj = new z; // 메모리 해제가 필요 없다. (자동) |
int A; Z obj;(클래스이다. 20byte) → C++은 동적할당과 정적 할당을 선택할 수 있다. obj = new z; delete z; obj = new z; |
'내장형 하드웨어 > C++' 카테고리의 다른 글
C++ - 참조, 복사 생성자, 연산자(operator) 오버로딩 (0) | 2011.11.17 |
---|---|
C++ 배열 포인터 복습 (0) | 2011.11.16 |
C++ - car class 추가, 다중상속, 유도(derivation), 타입 (0) | 2011.11.14 |
C++ 생성자, 소멸자, 상속 (0) | 2011.11.11 |
C++ 클래스, 접근속성, scope 연산자(::), inline (0) | 2011.11.10 |