삽질의 현장/- C++

#013_시(c)시(c)해서 C++?!_ 대입 연산자 중복 정의

shovelman 2015. 7. 12. 08:05

안녕하세요 삽잡이입니다~


이번 시간에는 대입 연산자에 대한 중복 정의를 해보려고 합니다...


대입과 같은 경우에는 ' = ' 의 연산자를 사용합니다.

우항에 있는 값을 좌항에 대입하는 것이죠... 즉 메모리를 덤핑한다고 합니다...

그렇다면 객체 또한 대입 연산을 할 수 있겠죠...

하지만 객체는 우리가 정의하는 타입에 의해 생성되니 당연하게 연산자 중복 정의를 해야합니다... 


하지만 그전에, 한가지 생각해보죠...

이전에도 배운 것이었는데... 


Sap sap1(4, 2);

Sap sap2 = sap1;


이와 같이 코드를 작성한다면, 어떤 듯이 될까요? 위에서 말한 대입연산? 

아닙니다... 

C++에서는 Sap sap2 = sap1; 이라고 하는 것 자체가

Sap sap2(sap1); 라고 할 수 있다고 했었습니다...

그말인 즉, 객체를 인자로 받으니 복사생성자가 호출 되는 것이죠...


그렇다면 C++ 에서 ' = ' 연산자가 대입 연산자라는 말은 어떻게 되는것인가요....

sap2와 같은 경우 객체로써 초기화가 진행되지 않은 상태이기에 생성자 호출이 필요한 시점입니다... 

따라서 이때에는 복사 생성자가 호출되는 것이지요...


이제 알 수 있습니다. 대입 연산자...

객체 사이에서 만큼은 생성 및 초기화가 진행된 객체끼리의 대입 연산시 

대입 연산자가 호출된다는 것을 말입니다...


Sap sap1(4, 2);

Sap sap2 (2, 3);

sap2 = sap1;


즉 위의 조건을 충족하게 될 때야 말로 비로소

sap2.operator=(sap1); 와 같이 된다는 사실을 알게됬습니다.



자... 그러면 이제 객체간의 관계에서 대입 연산자가 언제 호출되는지도 알게 되었습니다...

그럼 끝일까요? 아니니 이렇게 말을 하고있겠죠... 푸하하


대입 연산자는 우항에서 좌항으로 메모리를 그대로 덤핑한다고 했는데요

이때 문제가 발생할 수 있습니다...


아래 예시를 확인해보실까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Member1
{
    int num;
    char* name;
public:
    Member1(int num = 0const char *name = "")
    {
        this->num = num;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
    }
    ~Member1()
    {
        delete[] name;
    }
    void View()const
    {
        cout << "번호: " << num << " 이름:" << name << endl;
    }
};
 
void main()
{
    Member1 mm1(4"솹잡이");
    Member1 mm2(8"삽잡이");
    mm2 = mm1;
    mm1.View();
    mm2.View();
}
cs


이 코드의 결과는 어떻게 될까요?

하하 오류가 발생합니다... 왜일까요?


mm2 == mm1; 를 보시면 답이 나옵니다....


mm1과 mm2 객체는 모두 "솹잡이" 와 "삽잡이" (-_-;;) 라는 문자열을 동적으로 할당받았습니다...

즉, 각 객체의 name 은 해당 문자열들의 시작 주소를 가리키고 있다 이겁니다.


그런데 말입니다...

mm2과 mm1의 대입연산으로 인해서 class 내에 정의한 연산자 중복 정의가 없으니 

디폴트 대입 연산자를 사용하게 됩니다.


따라서 mm2에 name 멤버 변수가 가르키던 주소가 

mm1이 가리키는 주소를 가리키게 되는것입니다...


자... 음... 한 문자열을 서로 찜한 각 객체의 멤버 변수들....

근데 이때 막장 드라마가 시작됩니다...


mm2 객체가 가리키던 문자열은 버림받고...

mm2 객체가 원래 mm1이 가리키던 문자열의 주소와 함께 소멸되며 사라지게 됩니다...


이런 마음 아픈일이....

결국 mm1은 소멸될 때 가리키던 메모리주소에 원래 있어야되는 문자열이 있어야되는데 

이미 소멸되었기 때문에 또 소멸하게 되는 아이러니한 문제가 발생하게 됩니다....


이를 얕은 복사의 문제점이라고 하는데요,

따라서 깊은 복사를 할 필요성이 있습니다...


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Member1
{
    int num;
    char* name;
public:
    Member1(int num = 0const char *name = "")
    {
        this->num = num;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
    }
    ~Member1()
    {
        delete[] name;
    }
    void View()const
    {
        cout << "번호: " << num << " 이름:" << name << endl;
    }
 
    Member1 &operator=(const Member1 &mm1)
    {
        // 원래 있던 동적 메모리를 소멸한다.
        delete []name;
        num = mm1.num;
        // 새롭게 동적할당을 한다.
        name = new char[strlen(mm1.name) + 1];
        //mm1의 문자열을 복사한다.
        strcpy(name, mm1.name);
        return (*this);
    }
};
void main()
{
    Member1 mm1(4"솹잡이");
    Member1 mm2(8"삽잡이");
    mm2 = mm1;
    mm1.View();
    mm2.View();
}
cs



위 코드의 주석과 같이 메모리 누수를 막기 위해 메모리를 미리 해제하고, 

막장 드라마를 피하기 위해서 


새로운 동적 메모리를 복사할 문자열의 길이 +1 (\0) 만큼 할당하는 것이죠...

그리고 그대로 복사한다면....

위와 같은 막장 드라마를 막을 수 있게 됩니다.... 막장 드라마가 재미있긴 한데 말이죠... 허허...


아무튼... 프로그래밍상 사랑과 전쟁을 

여러분께서는 시청하셨습니다... 그리고 삽잡이 판사(;;)의 현명한 판단으로 

원만한 해결을 하여 4주후에 뵙지 않아도 됬군요... 뭔소린지 푸하하...


이상 삽잡이였습니다!