본문 바로가기
C++

[C++] 대입연산자

by Meaning_ 2022. 7. 9.
728x90
반응형

객체를 먼저 생성한다음에

다른 객체를 이 객체에 대입하려면 어떻게 해야할까?

객체가 생긴 시점에는 기본생성자가 호출이 된 다음에 따로 대입을 한것인데

이때는 복사생성자가 호출이 되지 않는다.

 

 

복사생성자 복습

https://we1cometomeanings.tistory.com/217?category=986307 

 

[C++기초] 깊은복사와 얕은 복사 ,복사생성자

얕은 복사(참조만 복사) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include using namespace std; int main() {     int* a = new int(5);     int* b = new int(3);     a = b;    ..

we1cometomeanings.tistory.com

변수의 경우에는 대입연산자 (=)를 통한 값의 복사는 문제가 되지 않지만 객체에서는 문제가 발생했다.

왜냐면 주소값만 복사하는 얕은 복사가 일어났기 때문이다.

s2라는 객체에 s1이라는 객체를 대입하고 싶으면 단순히 얕은복사만 해서는 안될 것이다.

그래서 해결방법이 "복사생성자"이다.  복사생성자는 새롭게 생성되는 객체가 원본객체와

같으면서도 완전한 독립성을 가지게 해준다. 

 

1. 객체가 함수에 인수로 전달될 때

2. 함수가 객체를 반환값으로 반환할 때

3. 새로운 객체를 같은 클래스 타입의 기존 객체와 똑같이 초기화할 때

 

++) 일반 변수 대입할때는 = 사용해도 되지만 포인터를 대입할때는 깊은복사 해줘야한다.(예를 들어 strcpy같은)

 

이제 이 등호를 다르게 정의해볼 것이다. 

근데 어떤걸 반환해야하는지에 대해 의문이 생긴다. 

 

rhs를 레퍼런스 변수로 

 

그러면 이전에 공부했던 연산자 오버로딩을 통해 다시 등호를 정의해볼 수 있다. 

이 함수의 반환 타입은 자기 자신이여야 한다. 그래야 등호연산이 수행될 수 있기 때문이다.

 

operator= 를 통해 연산자 오버로딩을 할 것이다. 결국에 s3=s1이 s3.operator=(s1)이 된다!

rhs는 String의 자료형을 가지고 있는 객체이다. s1이 매개변수로 들어오지만 사실상 s1과 rhs는 다른 객체이다.

operator= 함수 시작전에 rhs에 s1이 들어가는데 이때 복사가 된다.

복사를 할때 복사생성자가 호출이된다. 

복사생성자를 잘 보면 새로운 배열을 만들어주고 거기다 깊은 복사를 하는 아주 비효율적인 과정을 수행하고 있다.

이것을 해결할 수 있는게 "레퍼런스 변수"이다. 

 

애초에 레퍼런스 변수 사용이유가 매개변수로 객체를 넘겨주기 위함이다.

레퍼런스 변수는 s1의 값이 rhs에 복사가 되는 것이 아닌 rhs라는 레퍼런스 변수가  s1이라는 객체를 가리키고 있기 때문에 객체 복사가 일어나지 않는다. 

 

반환타입을 레퍼런스 변수로

 

리턴을 하면 임시객체를 생성한다. 임시객체가 형성되는 순간에 객체복사가 일어난다.(비효율)

반환타입에 레퍼런스를 넣어주면 객체복사를 줄여준다.

 

++) 레퍼런스를 사용한다 === 객체복사를 줄여준다.

 

 

이미 strData에 값이 존재하고 있다면? 

 

문제가 하나 있는데 strData이다. 이전까지 복사생성자는 새롭게 객체를 할당해주는 것이기 때문에

메모리 상의 공간이 존재하지 않는다.

근데 대입연산자는 말이 달라진다. 이미 strData가 가지고 있는 데이터가 있을수도 있다.

이럴때는 메모리를 해제하고 새로 메모리를 할당해줘야한다. 

strData가 null일 때는 애초에 delete 연산이 아무 동작을 하지 않는다.

 

이제 반환을 해볼 것인데 rhs를 반환하려고 보니 const String이여서 반환이 되지 않는다.

그래서 *this를 반환할 것이다.

this는 이 함수가 실행될 때  이 함수가 속해있는 객체의 주소값이다. 이것을 역참조하면 

rhs 값 자체가 반환될 것이다. 

 

결함 ) 자기 자신을 자기 자신에게 할당할 때? 

이럴땐 어떻게 해야할까?

 

operator= 함수로 돌아와보면

우선 delete[] strData를 하면 s4에 있는 "Hello"라는 데이터를 삭제한다.

그러고 다시 strData를 할당한다. 근데 여기서 rhs.len의 값은 지워지지 않아서 

길이가 6인 배열이 다시 생성된다. 

이제 strData는 새로 만들어진 길이가 6인 배열을 가리키게 된다.

 

srtcpy를 통해 rhs.strData를 strData에 복사하는데 아무 것도 없는 값(사실상 쓰레기값)을 복사하니까

쓰레기값이 출력되게 된다. 

 

자기자신을 대입하지 않기 위해서는 두 객체의 주소값이 같은지 비교하면된다.

값이 아니라 주소값이다! 두 객체가 같은 곳을 가리키고 있는지 비교하기 위해서이기 때문이다.

 

this==rhs를 하게 되면 rhs에서 그 객체 자체를 나타내기 때문에 &rhs를 해줘야한다. 

++) 매개변수로 받아온 &rhs는 레퍼런스변수인거고 , if문에 있는 &rhs는 rhs의 주소값이다. 

 

이러면 대입연산자 오버로딩이 실행되지 않아서 Hello가 정상적으로 출력된다.

 

최종코드

 

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <iostream>
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)
 
using namespace std;
 
 
class String {
public:
    String() {
//생성자 안에 문자열이 없을 때, 객체만 할당될 때 호출
        cout << "String() 생성자 호출" << endl;
        strData = NULL;
        len = 0;
    };
    String(const char* str) {
        //문자열 동적할당. 객체 자체가 생성될 때 호출됩니다. 생성자 안에 문자열이 있는경우
 
        cout << "String(const char* str) 생성자 호출" << endl;
        
    
        
        len = strlen(str);
        strData = new char[len+1];
        
        cout << "strData 할당" << (void*)strData << endl;
        
        
        strcpy(strData, str);//strData에 str의 내용을 넣어준다.(깊은복사)
 
 
    }
    ~String() {
        cout << "~String() 소멸자 호출" << endl;
        delete[] strData;
        cout << "strData 해제됨: " << (void*)strData << endl;
        strData = NULL;
    };
    String(const String &rhs) {
        //String s2=s1 복사생성자 호출할 때 
 
        cout << "String(String& rhs) 생성자 호출" << endl;
        strData = new char[rhs.len + 1];
        strcpy(strData, rhs.strData);//깊은복사
        len = rhs.len; //깊은 복사 (일반값)
    }
 
    String &operator=(const String &rhs) {
        //s3=s1 대입연산자 수행할 때 
 
        //객체가 같은 값을 가지고 있는지가 아닌 같은 주소값을 가리키고
        //있는지 판별해야함
        if (this != &rhs) {
            cout << "opertaor=(const String &rhs) 연산자 실행" << endl;
            delete[] strData;
            strData = new char[rhs.len + 1];
            strcpy(strData, rhs.strData);//깊은복사
            len = rhs.len; //깊은 복사 (일반값)
 
        }
        
        
 
        return *this;
 
    }
    char* GetStrData()const {
        return strData;
    }
 
    int GetLen()const {
        return len;
    }
 
private:
    char* strData;
    int len;
 
 
 
};
 
 
int main() {
 
    int val = 4;
    int& ref = val;
    cout << ref << endl;
    cout << &ref << endl;
 
    String s1="안녕!";
    //매개변수가 없는경우는 동적할당 안하는게 좋음
 
    String s2 =s1;//복사생성자
 
 
    //s2객체에 공간 할당이 안됨
    //s2도 똑같이 "안녕!" 인것을 보아 s1.strData에 들어있는 주소값이
    //똑같이 복사되었을것을 추정(얕은복사)
 
    String s3="Hello"//먼저 객체 할당 부터 들어감 "String(const char* str) 생성자 호출" 
    s3=s1; //그다음에 operator= 연산자 호출됨 
 
    String s4 = "Hello";
    s4 = s4;
 
 
    cout << s1.GetStrData() << endl;
    cout << s2.GetStrData() << endl;
    cout << s3.GetStrData() << endl;
    cout << s4.GetStrData() << endl;
    
 
    
 
 
    
    
}
cs

 

728x90
반응형

댓글