Basic/C/C++2012.02.25 21:38



 오랜만에 포스팅합니다~ 이번 2월달에는 블로그에 신경을 많이 못 써주었네요 ㅠㅠㅠ그래도 블로그 오픈 약 2달만에 2만명이라는 방문자를 달성했습니다 우와..... 열심히 하겠습니다! 정말 좋은 정보들 얻어가셨으면 좋겠다는게 제 바램입니다. 조만간 리뷰도 올리고 제품소개도 할 예정입니다^^ 학술블로그만 너무 올리는 것 같아서....ㅎㅎㅎ  자 그럼 오늘도 열공하겠습니다!

 [복사 생성자]

 int value1 = 10;
 int value2(10);

 첫 번째 문장과 두 번째 문장은 서로 동일합니다. C언어 스타일이냐 C++언어 스타일이냐만 다르지 기능은 완벽하게 똑같습니다. 

 객체로 넘어가보겠습니다. 그렇다면 클래스 AAA를 정의했다고 합시다.

 AAA a1(10);
 AAA a2 = 10;

 마찬가지로 객체의 생성에 있어서도 C스타일 초기화와 C++초기화가 가능합니다. 그러나 객체 생성과정에서는 AAA a2 = 10; 을 AAA a2(10); 으로 묵시적인 변환이 됩니다. 사실 결과적으로는 같다고 보셔도 무방합니다. 물론 차이를 보이를 경우도 있지만 일단은 결과적으로 두개의 코드는 동일하다고 생각하시면 됩니다~

 이번에는 생성자 오버로딩을 잠깐 살펴보도록 하겠습니다. 지루하실까봐 예제는 최소화 하도록 하겠습니다^-^ 다음과 같은 3개의 생성자중에 복사생성자가 숨어있습니다~

 
AAA()
 {
    cout<<" AAA() callin !!! "<<endl;
  }
 AAA(int i)
 {
    cout<<" AAA(int i) callin !!! "<<endl;
  }
 
AAA(const AAA& a)
 {
    cout<<" AAA(const AAA& a) callin !!! "<<endl;
  }

 생성자에 대해서는 이미 공부한바 있으니까 충분히 별도의 설명없이도 이해하실 거라고 생각하구요~ 그렇다면 복사 생성자를 얘기하기전에 여기서 무엇을 말하고 싶을걸까요? 세 번쩨 생성자의 모습이 복사생성자의 모습입니다. 즉, 자기 자신과 같은 형태(자료형)의 객체를 인자로 받을 수 있는 생성자를 복사생성자라고 합니다.

 여기서보면 매개 변수 선언에서 const가 붙어있는게 보이실 겁니다. 인자로 전달된 객체의 내용의 변경을 허용하지 않겠다는 선언입니다. 그렇다면 & 선언은 무엇일까요? 인자로 전달된 객체를 '레퍼런스'로 받겠다는 선언입니다. 레퍼런스도 중요하게 설명한적 있으니까 혹시 기억이 나지 않으시면 다시한번 레퍼런스 부분을 정독해보세요~~~

 [디폴트 복사 생성자]

이름만 들어도 대충 감이 오시죠~? 사용자가 특별히 정의해주지 않아도 이미 정의 되어있는 디폴트 복사 생성자라는 이야기입니다.

class point
{
   int x , y;
     public:
           point(int _x, int _y)
          {  x = _x; y = _y }
           void show
          { cout <<x<<' '<<y<<endl; }
 };

 int main(void)
{
  point p1(10,20);
  point p2(p1);

  return 0;
 }

 point p1(10,20);는 정수형 변수 2개를 인자로 받으면서 객체를 생성하고 있습니다. 어? 그러나 point p2(p1); 이라는 생성자는 따로 정의를 했나요? 정의하지 않았습니다. 때문에 컴파일 오류가 발생할거라고 생각하시지만 그렇지 않습니다. 그러나 결과값을 확인해보면 알겠지만 정상적으로 실행이되고 point p2(p1);이라는 문장이 정확하게 객체를 복사하고 있습니다.

 point(const point& p)
{
   x = p.x;
   y = p.y;
 }

 위와 같은 문장이 자동으로 삽입되었다는걸 추측할 수 있겠죠? 이렇게 자동으로 삽입되는 복사생성자를 가리켜 디폴트 복사 생성자라고 합니다. 물론 복사 생성자는 클래스에 따라서 조금씩 달라지기는 합니다. 즉 클래스마다 정의되어 들어가는 디폴트 복사 생성자의 형태는 다를 겁니다. 그러나 point 객체의 경우 디폴트 복사 생성자는 위 예제와 동일한 모양을 가지고 있습니다.

 [깊은 복사를 하는 복사 생성자]

 디폴트 복사 생성자가 있으니까 굳이 복사 생성자를 정의할 필요가 없다는 생각을 갖을 수도 있습니다. 그러나 디폴트 복사 생성자에는 부족한 부분이 존재합니다. 우리가 복사 생성자를 직접 정의해야되는 경우도 자주 발생하기도 하구요. 그림으로 설명하고 싶지만..... ㅠㅠㅠ 용서해주십쇼

 person이라는 클래스 많이 보셨죠? person 클래스의 객체 p1, p2를 생성했다고 합시다. 맴버 변수는 이름, 전화번호, 나이 정도가 있겠군요.

 person p1( "boxbop" , "010-1234-5678", 10);
 person p2=p1; // person p2(p1); 동일한 문장

여기서 중요한건 이름과 전화번호 입니다. 맴버변수의 자료형이 주소값이기 때문이죠. char* name 이나 char *phone 으로 정의되어 있습니다. 객체를 초기화할때 boxbop 과 010-1234-5678은 메모리에 저장되어있고 저장되어있는 위치의 주소값을 사용하는거 다들 아시죠? 그러나 p2(p1)과 같은 문장으로 복사를 하게되면 메모리에 있는 주소값만 복사를 하는겁니다. 실제로 메모리에는 boxbop 과 010-1234-5678은 각각 하나씩만 존재를 하게되죠. 진정한 복사는 이러한 내용도 모두 똑같이 그리고 하나씩 더 존재를 해야되는거죠? 그러나 위와 같은 문장으로는 메모리의 주소값만 복사할 뿐 실질적인 메모리의 내용까지는 복사하지 않습니다. 이런걸 얕은 복사와 깊은 복사라고 합니다. 

 이러한 얕은복사의 문제점 때문에 프로그래머는 복사생성자를 다시 정의할 필요성이 있는 겁니다. 그러면 깊은 복사를 한번 구현해보겠습니다.

 person::person(const person& p)
{
    name = new char[strlen(p.name)+1];
    strcpy(name, p.name);

    phone = new char[strlen(p.phone)+1];
    strcpy(phone, p.phoen);
    age = p.age;
 }

 전에 한번 이런 생성자를 구현한적이 기억나시나요~? 생성자 내에서 동적 할당을 하면 반드시 제공해야되는 것이 있습니다!!! 소멸자입니다. 잊으시면 안됩니다~ 그래야 메모리 누수가 발생하지 않습니다. 아래와 같은 소멸자 꼭 잊지마시구요~

 person::~person()
{
    delete []name;
    delete []phoen;
 }

 [복사 생성자가 호출되는 시점]

복사 생성자가 호출되는 시점은 다음과 같이 세 가지 형태로 분류할 수 있습니다.
 1. 기존에 생성된 객체로 새로운 객체를 초기화하는 경우
 2. 함수 호출 시 객체를 값에 의해 전달하는 경우
 3. 함수 내에서 객체를 값에 의해 리턴하는 경우
각각의 경우를 예제로 살펴보도록 하겠습니다.

1. 기존에 생성된 객체로 새로운 객체를 초기화하는 경우

AAA obj1;
AAA obj2 = obj1;

2. 함수 호출 시 객체를 값에 의해 전달하는 경우

void function(AAA a)
{
    a.showdata();
 }

 int main()
{
    AAA obj(30);
    function(obj);

    return 0;
 }

 여기서 볼드처리된 부분을 좀 더 설명해보겠습니다. 여기서 첫 번째로 인자를 받을 매개 변수를 위한 메모리 공간이 할당됩니다. 매개변수가 a이므로 a를 위해 할당된 메모리 공간이 형성됩니다. 사실 매개 변수 a는 클래스의 객체입니다만 정확하게는 객체라고 표현하지 않습니다. 생성자의 호출이 없었기 때문이죠. 두 번째로 전달 인자 값의 복사입니다. 위에서 생성한 객체 obj가 함수의 전달 인자로 넘어 왔으므로, obj가 지니고 있는 값을 매개 변수 a에 복사해야 합니다. 매개 변수 a의 복사 생성자를 호출하면서 복사의 대상이 되는 객체 obj를 인자로 전달하게 됩니다. 여기서 중요한 것은 매개 변수로 생성되는 객체(a)의 복사 생성자가 호출되는 것입니다. 이제는 매개 변수 a는 객체라고 불릴 수 있게 됬습니다.

3. 함수 내에서 객체를 값에 의해 리턴하는 경우

int function(void)
{
    int a = 10;
    return a;
 }

int main()
{
    cout<<function(void)<<endl;
    return 0;
 }

function 함수가 리턴한 값을 출력하고 있습니다. 리턴된 값을 출력할 수 있다는 것은 함수를 호출한 영역으로 값이 리턴되었다는 이야기입니다. 여기서 중요한 사실은 한가지 입니다. 리턴되는 값은 받아주는 변수가 없더라도, 함수를 호출한 영역으로 복사되어 넘어갑니다. 스택 영역에 main 함수의 영역과 function 함수의 영역이 있을때 리턴 되는 값은 function 함수의 영역에서 일단은 main 함수의 영역으로 넘어간다는 이야기입니다.

AAA function(void)
{
    AAA a(10);
    return a;
 }

 int main()
{
    function();
    return 0;
 }

위 예제에서 function 함수는 객체 a를 생성한 다음, 이 객체를 값에 의해 리턴하고 있습니다. 따라서 함수를 호출한 영역으로 객체가 복사되어 넘어갈 것 입니다. a객체의 복사본이 function 함수 영역에서 main 함수 영역으로 넘어가겠죠? 그렇다면 과연 어떻게 a 객체의 복사본이 생성되느냐 하는 것 입니다. 복사본도 객체기 때문에 복사본을 위한 메모리 공간이 할당되어야 합니다. 그 다음에는 복사본의 생성자가 호출되는데 원본 객체 a를 인자로 전달받을 수 있는 생성자, 즉 복사 생성자가 호출됩니다. 즉 객체 a를 인자로 전달하여 복사본에 똑같이 담아냅니다. 직관적으로 생각했던 것과 비슷하죠~?아마? 이번 복사 생성자에 대한 내용은 이해가 잘 안된다고 걱정할 필요는 없습니다. 나중에 천천히 이해해도 되니까요~

 이상으로 복사 생성자에 대한 내용을 마치도록 하겠습니다. 딴짓거리 하면서 포스팅 하느라 생각보다 오래걸렸네요^-^;;



Posted by boxbop@gmail.com boxbop