본문 바로가기
코스웨어/14년 스마트컨트롤러

2014.10.13 일일 교육보고 - 김대희 (3)

by 알 수 없는 사용자 2014. 10. 13.
728x90
반응형


생성자는 객체생성과 동시에 자동으로 생성된다.


※ 첨부된 여러개의 예제 파일명이 모두 'main.cpp'이므로 저장시 주의

main.cpp 


#include <iostream>
using namespace std; // space operator


class SoSimple // 클래스 만듬
{
private:
  int num1;
  int num2;
public:
  SoSimple(int n1,int n2):num1(n1), num2(n2)
  {
  }

  SoSimple(SoSimple &copy):num1(copy.num1), num2(copy.num2) // 복사생성자
  {
    cout<<"복사생성자가 호출되었습니다.\n";
  }
  void ShowSimpleData()
  {
    cout<<num1<<endl;
    cout<<num2<<endl;
  }
};

int main()
{
  SoSimple sim1(15,30); // 일반적인 객체생성
  cout<<"생성 및 초기화 직전\n";

  // sim2객체를 생성함과 동시에 sim1을 대입.
  SoSimple sim2 = sim1;// SoSimple sim2(sim1);

  cout<<"생성 및 포기화 직후\n";
  sim2.ShowSimpleData();

  return 0;
}


결과 : 


생성 및 초기화 직전

복사생성자가 호출되었습니다.

생성 및 포기화 직후

15

30


  • 복사생성자란?

위 예제를 언급하여 말하자면 SoSimple sim2 =  sim1; 라는 코드를 썼을때

일반적으로 생각하면 이럴 것이다.


어쨋든 sim2의 객체를 생성했으니까 정상적으로 생성 되고

그냥 sim1의 객체를 그대로 복사하는 것 아닌가?


이것이 틀린것은 아니지만 간과하지 말아야 할 부분이 있다.

C++은 이 구문을 다음구문으로 바꾼다.


SoSimple sim2 =  sim1; -----> SoSimple sim2(sim1);


그리고 생각한 것 처럼 객체가 생성된 후 복사가 일어나는 것이 아니고

객체가 생성됨과 동시에 객체간의 바이너리 복사가 일어난다.

이에 입각하면 sim2는 어떤 생성자를 호출하게 될까가 관점이 되겠다.


SoSimple 클래스를 보면 생성자가 두개다.

한개는 내용없는 오버로딩된 생성자, 하나는 생소해보이는 복사생성자 라는 것.


결과에서 보았듯이 위처럼 대입연산이 일어날때 복사생성자가 호출됬다는 것을 결과를 보면 확인할 수 있다.








  • 복사생성자의 레퍼런스를 꼭 붙혀야 한다.

그리고 복사생성자 인자에는 레퍼런스(&)를 곡 붙여주어야 한다.

레퍼런스를 붙히지 않는다는 것은 참조하지 않는다는 것이고, 그것은 곳 인자에서 SoSimple 객체가 하나 더

만들어 진다는 것을 의미한다.


SoSimple(SoSimple r)

{

}


위와같은 생성자가 있다고 가정한다면 무한으로 객체가 생성되는 엉뚱한 프로그램이 될 것이다.

그러므로 복사생성자에 레퍼런스(&)를 붙혀주는 것이다.


다행히도 C++컴파일러는 &를 붙혀주지 않으면 에러를 발생시켜주므로, 실제로 이것이 일어날 일은 없다.

하지만 알아두고 나쁠 건 없다.








  • 예제의 복사생성자를 잘 살펴보자.
  SoSimple(SoSimple &copy):num1(copy.num1), num2(copy.num2) // 복사생성자
  {
    cout<<"복사생성자가 호출되었습니다.\n";

  }


멤버이니셜라이저를 잘 보면 이해가 빠를 것이다.

sim1 객체가 sim2객체에 대입되었으므로 copy.num1 은 즉 sim1.num1 이 되겠다. 그래서 '15'가 출력 되었다.

num2 또한 마찬가지다.







  • 동적할당을 받고 객체간의 대입을 했을 때는 더욱 주의

다음 예제를 보자


#include <iostream>
#include <cstring> // strcpy() 사용을 위해..
using namespace std;

class smart
{
public:

  char * p;

  smart() // 디폴트생성자
  {
    p = new char[3]; // 동적할당 받음
    strcpy(p,"HI");
    cout<<"smart() : default 생성잡니다\n";
  }

  smart(smart &R) // 복사생성자
  {
    cout<<"smart() : 복사생성잡니다\n";
  }

  ~smart()
  {
    cout<<"smart() : 소멸잡니다\n";
    delete[] p;
  }
};

int main()
{
  smart obj1; // 디폴트생성자 호출
  smart obj2; // 디폴트생성자 호출

  obj2 = obj1; // 객체끼리 바이너리 복사됨.
         // 구조체의 값 대입과 같다.
         // 대입연산자 호출
  
  
  return 0;
}


결과 : 



위 예제는 깔끔하게 에러가 난다.


이유인 즉슨 다음 그림과 같다.

먼저 obj2 = obj1; 에서는 바이너리 복사가 일어나므로

각 obj2역시 동적할당을 받지만, 정작 obj2.p가 가리키는 곳은

obj1.p 가 가리키는 곳과 같은곳을 가리키고 있는다는 점이 문제다.


이렇게 되면 obj2가 할당받는 Heap영역의 "Hi"는 잃어버리고 우주를 떠돌게 됨

다음 그림으로 이해를 돕겠다.



문제는 소멸자가 호출될 때에도 있다.

obj2가 가장 최근에 호출 되었으므로 obj2객체가 종료하는 시점에서 '소멸자'가 호출 될 것이다.

'p'가 가리키는 곳을 delete시켜버리는데 문제는 그다음 obj1.p 가 어디를 가리키고 있냐는 것이다.

obj1.p 는 허공을 가리키게 되는 것이다.


다음 그림으로 정리하자




  • 수정한 소스코드
#include <iostream>
#include <cstring> // strcpy() 사용을 위해..
using namespace std;

class smart
{
public:

  char * p;

  smart() // 디폴트생성자
  {
    p = new char[3]; // 동적할당 받음
    strcpy(p,"HI");
    cout<<"smart() : default 생성잡니다\n";
  }

  smart(smart &R) // 복사생성자
  {
    p = new char[3]; // 동적할당 받음

    strcpy(p,R.p);   // R.p를 현재 'p'에 복사함으로써 
             // 'p'를 제대로 가리키고 있는 중.

    cout<<"p = "<<p<<"..........smart() : 복사생성잡니다\n";
  }

  ~smart()
  {
    cout<<"smart() : 소멸잡니다\n";
    delete[] p;
  }
};

int main()
{
  smart obj1;
  smart obj2;
  smart obj3 = obj2;

  
  return 0;
}


결과 : 

smart() : default 생성잡니다

smart() : default 생성잡니다

p = HI..........smart() : 복사생성잡니다

smart() : 소멸잡니다

smart() : 소멸잡니다

smart() : 소멸잡니다


에러가 뜨지 않는 소스코드다 위의 복사생성자를 자세히 다룰 필요가 있다.


  smart(smart &R) // 복사생성자
  {
    p = new char[3]; // 동적할당 받음

    strcpy(p,R.p);   // R.p를 현재 'p'에 복사함으로써 
             // 'p'를 제대로 가리키고 있는 중.

    cout<<"p = "<<p<<"..........smart() : 복사생성잡니다\n";

  }





















버추얼포트를 실행한다.


버추얼포트 다운



버추얼 포트를 만들고나서 

다음 소스코드를 컴파일 한다.



main.c


#include <windows.h>
#include <stdio.h>

DWORD WINAPI Thread_Read(LPVOID);

HANDLE hComm;

int main()
{
   u_char caString[5]= "하이~";
   DWORD dwWritten;            // double word Written 쓰기 후 실제 쓴 바이트 수 저장 공간
   DCB sPState;               // struct Port State 시리얼 포트 상태 저장
   COMMTIMEOUTS cTime;
   DWORD ThreadID;
   HANDLE hThread;

   hComm = CreateFile("COM6"
               , GENERIC_READ|GENERIC_WRITE
               ,0
               ,NULL
               ,OPEN_EXISTING
               ,FILE_ATTRIBUTE_NORMAL
               ,0);
   if(INVALID_HANDLE_VALUE == hComm)
   {
      printf("포트 열 수 없음\n");
      return 0;
   }
   if(0 == SetupComm(hComm,4096,3096))
   {
      printf("버퍼 설정 에러\n");
      CloseHandle(hComm);
      return 0;
   }
   if(0== PurgeComm(hComm,PURGE_TXABORT | PURGE_TXCLEAR))
   {
      printf("버퍼 초기화 에러\n");
      CloseHandle(hComm);
      return 0;
   }

   sPState.DCBlength = sizeof(sPState);

   if(0 == GetCommState(hComm, &sPState))
   {
      printf("시리얼 상태 읽기 에러\n");
      CloseHandle(hComm);
      return 0;
   }

   sPState.BaudRate = CBR_38400;   //속도
   sPState.ByteSize = 8;         //바이트크기
   sPState.Parity   = EVENPARITY;   //패리티 짝수
   sPState.StopBits = ONESTOPBIT;   // 스톱비트

   cTime.ReadIntervalTimeout = MAXDWORD; // READFILE 넌블럭킹
   cTime.ReadTotalTimeoutMultiplier = 0// READFILE 넌블럭킹..READ함수의 대기시간
   cTime.ReadTotalTimeoutConstant = 0// READFILE 넌블럭킹

   cTime.WriteTotalTimeoutMultiplier = 0// WRITE FILE '블럭킹'
   cTime.WriteTotalTimeoutConstant = 0// WRITE FILE '블럭킹'
   // '0'을 수동으로 넣어주지않으면 쓰레기값 들어감

   SetCommTimeouts(hComm, &cTime); // 대기설정..지금 대기하는게 아님, 설정만..


   if(0 == SetCommState(hComm, &sPState))
   {
      printf("시리얼 상태 설정 에러\n");
      CloseHandle(hComm);
      return 0;
   }

   hThread = CreateThread(NULL, 0, Thread_Read, NULL, 0&ThreadID);


   while(1)
   {
     caString[0= getch();
     
     if(0 == WriteFile(hComm, caString, 1&dwWritten,0))
     {
      printf("쓰기 에러\n");
     }
     else
     {
      printf("쓰기 성공\n");
     }
   }

   CloseHandle(hComm);
   return 0;
}



DWORD WINAPI Thread_Read(LPVOID NotUse)
{
  char tt =0;
  DWORD dwRead;

  while(1)
  {
    Sleep(50); // CPU점유율을 줄이기 위한 일시정지
    ReadFile(hComm, &tt,1,&dwRead,NULL);
    if(0 != dwRead)
    {
      putchar('[');
      putchar(tt);
      putchar(']');
    }
  }
}


컴파일 하고 실행파일을 실행 한다.

소스코드 내에서는 포트번호가 'COM6'으로 되어 있음에 유의 한다.


시리얼 패드를 실행한다.



위와 같이 설정을 맞추어 준 후

연결하기를 누른다.


위 소스코드로 컴파일 한 main.exe가 실행중이어야 한다.



이번엔 반대로 주고받아보겠다.





아몰라




728x90