내장형 하드웨어/C++

C++ - 객체 소멸 순서, 객체 생성(정적, 동적 할당-malloc, new)

동화다아아 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 car
{
      int K = 100;
}
struct car
{
     int K = 100;
}
 → 당연히 둘다 에러이다. 구조체와 마찬가지로 class도 메모리에 존재하지 않으므로 클래스 내 변수 초기화는 안된다.
 → 따라서 생성자 내에서 초기화 해야 한다.
 → 헷갈릴 때는 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;
}

 → 
 → 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; 의 경우는 가능하게 된다.

 → 위와 같은 자동 캐스팅은 남발하면 문제가 발생하게 된다.
B BB[10];
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
}

 → 위와 같이 생성자와 소멸자를 클래스의 밖으로 빼고 클래스 내에 선언해 준 다음
 → 밖으로 뺀 소멸자의 앞에다가 스코프 연산자(::)를 사용해 주면 사용이 가능하다.

※ 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);
  }



→ 다음으로 전역, 지역, 함수의 객체 생성 및 소멸의 순서를 확인해 본다.
→ 예제
#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-><< " : A class 생성자 호출" << endl;
}
A::A(int i, int j)
{
  (*this).i = i;
  (*this).j = j;
  cout << this-><< " : A(int, int) class 생성자 호출" << endl;
  cout << this-><< " : A(int, int) class 생성자 호출" << endl;
}
  
A::~A()
{
  cout << this-><< " : 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 함수
 → 소멸 순서 : 지역 -> 함수 -> 전역

 → 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;