'c언어'에 해당되는 글 72건

  1. 2015.04.22 C++의 복사 생성자 (copy constuctor)
  2. 2015.04.22 C++의 객체간 대입

  C++의 클래스에는 복사 생성자 (copy constructor)라는 것이 있는데 일반 생성자에 비해서 처음 C++을 익힐 때 그 동작에 대해서 간과하기 쉽다. 하지만 그 동작에 대해서 꼼꼼하게 살펴보지 않으면 논리적 오류가 만들어지기 쉽다.


  복사생성자가 호출되는 경우는 다음과 같은 경우가 있다.


-----------------------------------------------------------------

ClassOne c1, c2; // c1이 만들어지면서 일반 생성자를 호출

ClassOne c3 = c1;// ① 선언과 동시에 대입이 일어날때 복사 생성자를 호출

ClassOne c4(c1); // 위와 동일하게 복사생성자를 호출한다. 즉, ClassOne c4=c1 과 완전히 동일하다.

...

c2 = func(c1); //

...

ClassOne func(ClassOne co) { // ② 함수의 입력 인수 co 는 복사생성자로 생성됨

...

ClassOne cr;

....

return cr; // ③ cr을 넘겨줄 때도 복사 생성자 호출

}

------------------------------------------------------------------


사용자가 작성하지 않은 경우 디폴트 복사 생성자가 자동으로 만들어지지만 얕은 복사를 수행한다. 이 경우 멤버 변수에 동적으로 메모리가 할당되는 포인터라도 있다면 문제가 발생한다.


  따라서 사용자가 포인터의 내용까지도 따로 복사해 주는 깊은 복사를 수행하는 복사생성자를 항상 작성해 두는 것이 바람직하다.

[#00064]


Posted by 살레시오
,

  이미 생성된 인스턴스에 다른 인스턴스를 대입할 때 대입 연산자(=)가 이용되며 실제로는 복사가 수행된다. 다음과 같은 간단한 클래스를 고려해 보자.


-------------------------------------------

class Led {

    public :

        int pin;

        char *name;

};

-------------------------------------------


이 클래스는 복사 생성자도 없고 =연산자도 오버로딩되지 않았다. 이 경우 디폴트=연산자 함수는 얕은 복사를 수해하게 된다. 이제 다음과 같은 두 예를 보자


-------------------------------------------

//ex1

Led led1;

led1.pin=13;

Led led2 = led1; // 복사생성자 호출


//ex2

Led led1, led2;

led1.pin=13;

led2 = led1; // 대입 함수 호출

-------------------------------------------


<ex1>에서 led2가 생성될 때는 복사생성자가 실행되고  <ex2>에서는 대입 연산 함수가 호출된다.


하지만 멤버변수에 포인터가 있으므로 여기에 문자열이 저장된 경우 얕은 복사는 문제가 일어나게 된다. 


-------------------------------------------

Led led1, led2;

led1.name = new char[3];

strcpy(led1.name, "hi");


led2 = led1; // 얕은 복사가 일어나므로 문제를 야기함.

-------------------------------------------


심지어 깊은 복사를 수행하는 복사생성자가 정의되어 있어도 대입 연산자는 얕은 복사만을 수행하므로 주의해야 한다. 즉, 대입연산은 자동으로 사용자가 정의한 복사 생성자를 호출하지 않는다. 아래와 같이 깊은 복사를 수행하는 복사 생성자를 작성했다고 하자.


-------------------------------------------

class Led {

    public :

        int pin;

        char *namer;

        Led(const Led& src) {

            pin = src.pin;

            name = new char[strlen(src.name)+1];

            strcpy(name, src.name);

        }

};

-------------------------------------------


복사 생성자는 생성자이므로 인스턴스가 새로 생성될 때 수행된다. 따라서 name필드에 이전에 메모리를 할당한 적이 없을 것이니 해제할 필요도 없다.


  이렇게 작성한 뒤에서 대입연산은 여전히 얕은 복사를 수행하게 된다. 즉, 복사생성자와 대입연산자는 별개이다.  대입 시에도 깊은 복사를 수행하려면 대입연산자를 오버로딩하여 사용자가 작성해주어야 한다. 한 가지 주의할 점은 대입연산자는 이미 생성된 인스턴스에다 복사해 넣는 것이므로 기존의 문자열을 해제하는 코드가 추가로 필요하다는 것이다. 이것이 대입연산자와 복사생성자의 차이이다.


-------------------------------------------

Led& Led::operator= (const Led& src) {

    if (this == &src) return *this; // 자기 대입 방지


    pin = src.pin;

    i(name != NULL) delete[] name; // (주의)

        name = new char[strlen(src.name)+1];

        strcpy(str, src.name);


    return *this;

}

-------------------------------------------


이제는 대입연사자를 사용하면 깊은 복사가 일어나게 된다. 만약 복사 생성자와 코드가 많이 중복된다면 중복되는 부분은 private 멤버 함수로 따로 작성하는 것이 더 효율적일 것이다.

[#00063]


Posted by 살레시오
,