도순씨의 코딩일지

C++ :: 디폴트 대입 연산자, 배열의 인덱스 연산자 오버로딩, const 함수 오버로딩 본문

𝐏𝐑𝐎𝐆𝐑𝐀𝐌𝐌𝐈𝐍𝐆/𝐂++

C++ :: 디폴트 대입 연산자, 배열의 인덱스 연산자 오버로딩, const 함수 오버로딩

도순씨 2020. 8. 30. 00:00

🌼 디폴트 대입 연산자

대입 연산자의 대표적인 특성은 다음과 같습니다.

💡 정의하지 않으면 디폴트 대입 연산자가 삽입된다.

💡 디폴트 대입 연산자는 멤버 대 멤버의 복사(얕은 복사)를 진행한다.

💡 정의하지 않으면 디폴트 대입 연산자가 삽입된다.

 

pos2 = pos1은 멤버함수방식의 오버로딩을 기준으로 다음의 형태로 해석됨을 알 수 있습니다.

1
pos2.operator = (pos1);
cs

 

⭐️ FirstOperationOverloading.cpp

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
#include <iostream>
using namespace std;
 
class First{
private:
    int num1, num2;
public:
    First(int n1 = 0int n2 = 0) : num1(n1), num2(n2) {}
    void ShowData() { cout << num1 << ", " << num2 << endl;}
};
 
class Second{
private:
    int num3, num4;
public:
    Second(int n3 = 0 , int n4 = 0) : num3(n3), num4(n4){}
    void ShowData(){ cout << num3 << ", " << num4 << endl;}
    Second& operator = (const Second &ref){
        cout << "Second& operator = ()" << endl;
        num3 = ref.num3;
        num4 = ref.num4;
        return *this;
    }
};
 
int main(void){
    First fsrc(111222);
    First fcpy;
    Second ssrc(333444);
    Second scpy;
    fcpy = fsrc;
    scpy = ssrc;
    fcpy.ShowData();
    scpy.ShowData();
 
    First fob1, fob2;
    Second sob1, sob2;
    fob1 = fob2 = fsrc;
    sob1 = sob2 = ssrc;
 
    fob1.ShowData();
    fob2.ShowData();
    sob1.ShowData();
    sob2.ShowData();
    return 0;
}
cs

 

⭐️ FirstOperationOverloading.cpp 실행결과

1
2
3
4
5
6
7
8
9
Second& operator = ()
111, 222
333, 444
Second& operator = ()
Second& operator = ()
111, 222
111, 222
333, 444
333, 444
cs

 

🌼 디폴트 대입 연산자의 문제점

⭐️ AssignShallowCopyError.cpp

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
#include <iostream>
#include <cstring>
using namespace std;
 
class Person{
private:
    char * name;
    int age;
public:
    Person(char * myname, int myage){
        int len = strlen(myname) + 1;
        name = new char[len];
        strcpy(name, myname);
        age = myage;
    }
    void ShowPersonInfo() const{
        cout << "이름: " << name << endl;
        cout << "나이: " << age << endl;
    }
    ~Person(){
        delete []name;
        cout << "called destructor!" << endl;
    }
};
 
int main(void){
    Person man1("Lee dong woo"29);
    Person man2("Yoon ji yul"22);
    man2 = man1;
    man1.ShowPersonInfo();
    man2.ShowPersonInfo();
    return 0;
}
cs

 

⭐️ AssignShallowCopyError.cpp 실행 결과

1
2
3
4
5
이름: Lee dong woo
나이: 29
이름: Lee dong woo
나이: 29
called destructor!
cs

 

객체는 두 개가 생성이 되었는데, 소멸자는 한 번만 실행되었습니다. 무언가 문제가 있음을 알 수 있습니다.

문제점은 29번째 라인에서 시행되는 얕은 복사에 있습니다. 얕은 복사는 하나의 문자열을 두 객체가 동시에 참조하는 상황을 만들게 됩니다. 이때문에 두 가지 문제점이 발생하게 됩니다.

 

💡 문자열 " Yoon ji yul"을 가리키던 문자열의 주소 값을 잃게 된다

💡 얕은 복사로 인해서 객체 소멸과정에서 지워진 문자열이 중복 소멸된다.

 

 

🌼 상속 구조에서의 대입 연산자 호출

대입연산자는 생성자가 아닙니다. 유도 클래스의 대입 연산자에 아무런 명시를 하지 않으면 기초 클래스의 대입연산자가 호출되지 않습니다. 예제 하나를 살펴보도록 합시다.

 

⭐️ InheritAssignOperation.cpp

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
#include <iostream>
using namespace std;
 
class First{
private:
    int num1, num2;
public:
    First(int n1 = 0int n2 = 0) : num1(n1), num2(n2){}
    void ShowData() { cout << num1<<", " << num2 << endl;}
 
    First& operator=(const First& ref){
        cout << "First& operator = ()" << endl;
        num1 = ref.num1;
        num2 = ref.num2;
        return *this;
    }
};
 
class Second : public First{
private:
    int num3, num4;
public:
    Second(int n1, int n2, int n3, int n4) : First(n1, n2), num3(n3), num4(n4){}
    void ShowData(){
        First :: ShowData();
        cout << num3 << ", " << num4 << endl;
    }
    /*
    Second& operator=(const Second& ref {
        cout << "Second& operator=()" << endl;
        num3 = ref.num3;
        num4 = ref.num4;
        return * this
    }
     */
};
 
int main(void){
    Second ssrc(111222333444);
    Second scpy(0000);
    scpy = ssrc;
    scpy.ShowData();
    return 0;
}
cs

 

⭐️ InheritAssignOperation.cpp 실행 결과

1
2
3
First& operator = ()
111, 222
333, 444
cs

 

유도 클래스에 삽입된 디폴트 대입 연산자가 기초 클래스의 대입연산자까지 호출했습니다.

이번에는 주석을 해제하고 프로그램을 실행해봅시다.

 

1
2
3
Second& operator=()
0, 0
333, 444
cs

위 예제들을 통해서 우리는 다음과 같은 사실을 알 수 있습니다

💡유도 클래스의 대입 연산자 정의에서, 명시적으로 기초 클래스의 대입 연산자 호출문을 삽입하지 않으면, 기초 클래스의 대입 연산자는 호출되지 않아서, 기초 클래스의 멤버 변수는 멤버 대 멤버의 복사 대상에서 제외된다.

 

🌼 이니셜라이저와 성능 향상

이니셜라이저를 사용하면 왜 성능이 향상될까요? 다음 예제를 살펴봅시다.

 

⭐️ Improvelnit.cpp

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
#include <iostream>
using namespace std;
 
class AAA{
private:
    int num;
public:
    AAA(int n = 0) : num(n){
        cout << "AAA(int n = 0" << endl;
    }
    AAA(const AAA& ref) : num(ref.num){
        cout << "AAA(const AAA& ref)" << endl;
    }
    AAA& operator=(const AAA& ref){
        num = ref.num;
        cout << "operator=(const AAA& ref)" << endl;
        return *this;
    }
};
 
class BBB{
private:
    AAA mem;
public:
    BBB(const AAA& ref) : mem(ref){}
};
 
class CCC{
private:
    AAA mem;
public:
    CCC(const AAA& ref) {mem = ref;}
};
 
int main(void){
    AAA obj1(12);
    cout << "*********************" << endl;
    BBB obj2(obj1);
    cout << "*********************" << endl;
    CCC obj3(obj1);
    return 0;
}
cs

 

⭐️ Improvelnit.cpp 실행 결과

1
2
3
4
5
6
AAA(int n = 0
*********************
AAA(const AAA& ref)
*********************
AAA(int n = 0
operator=(const AAA& ref)
cs

 

실행 결과를 확인해 봅시다. BBB 객체의 생성 과정에서는 복사 생성자만 호출되었는데 CCC 객체 생성과정과에서는 생성자와 대입연산자까지 호출되었습니다. 왜 이런 결과가 도출되었을까요? CCC 객체의 생성과정을 살펴보도록 합시다.

 

먼저 CCC 클래스의 생성자를 봅시다.

1
CCC(const AAA& ref) {mem = ref;}
cs

 

생성자의 몸체부분에서 대입연산을 통한 초기화를 진행하면, 선언과 초기화를 각각 별도의 문장에서 진행하는 형태로 생성됩니다. 그래서 생성자와 대입 연산자가 각각 한 번씩 호출된 것입니다.

이니셜라이저를 이용하여 초기화를 진행하면 함수의 호출 횟수를 줄일 수 있고 초기화의 과정을 단순화시킬 수 있으므로 효율적입니다.

 

🌼 배열 클래스

⭐️ArrayClass.cpp

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
#include <iostream>
#include <cstdlib>
using namespace std;
 
class BoundCheckIntArray{
private:
    int * arr;
    int arrlen;
public:
    BoundCheckIntArray(int len) : arrlen(len){
        arr = new int[len];
    }
    int& operator[] (int idx){
        if(idx < 0 || idx >= arrlen){
            cout << "Array index out of bound exception" << endl;
            exit(1);
        }
        return arr[idx];
    }
    ~BoundCheckIntArray(){
        delete []arr;
    }
};
 
int main(void){
    BoundCheckIntArray arr(5);
    for(int i = 0 ; i < 5 ; i++)
        arr[i] = (i+1* 11;
    for(int i = 0 ; i < 6 ; i++)
        cout << arr[i] << endl;
    return 0;
}
cs

 

⭐️ArrayClass.cpp 실행결과

1
2
3
4
5
6
11
22
33
44
55
Array index out of bound exception
cs

 

마지막 줄에서 볼 수 있듯 범위에서 벗어난 배열의 접근이 있었음을 알 수 있습니다. 

 

🌼 객체의 저장을 위한 배열 클래스 정의

⭐️ StablePointObjArray.cpp

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
#include <iostream>
#include <cstdlib>
using namespace std;
 
class Point{
private:
    int xpos, ypos;
public:
    Point(int x = 0int y = 0) : xpos(x), ypos(y){}
    friend ostream& operator<<(ostream& os, const Point& pos);
};
 
ostream& operator<<(ostream& os, const Point& pos){
    os << '[' << pos.xpos <<", " << pos.ypos << ']' << endl;
    return os;
}
 
class BoundCheckPointArray{
private:
    Point * arr;
    int arrlen;
 
    BoundCheckPointArray(const BoundCheckPointArray& arr) {}
    BoundCheckPointArray& operator=(const BoundCheckPointArray& arr) {}
 
public:
    BoundCheckPointArray(int len) : arrlen(len){
        arr = new Point[len];
    }
    Point& operator[](int idx){
        if(idx < 0 || idx >= arrlen){
            cout << "Array index out of bound exception"<<endl;
            exit(1);
        }
        return arr[idx];
    }
    Point operator[](int idx) const{
        if(idx < 0 || idx >= arrlen){
            cout << "Array index out of bound exception" << endl;
            exit(1);
        }
        return arr[idx];
    }
    int GetArrLen() const { return arrlen; }
    ~BoundCheckPointArray() {delete []arr;}
};
 
int main(void){
    BoundCheckPointArray arr(3);
    arr[0= Point(34);
    arr[1= Point(56);
    arr[2= Point(78);
 
    for(int i = 0 ; i < arr.GetArrLen(); i++)
        cout << arr[i];
    return 0;
}
cs

 

⭐️ StablePointObjArray.cpp 실행결과

1
2
3
[3, 4]
[5, 6]
[7, 8]
cs

 

Point 객체로 이루어진 배열을 선언하고 디폴트 대입 연산자가 호출되어 멤버 대 멤버 복사가 진행되었다.

 

⭐️ StablePointPtrArray.cpp

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
#include <iostream>
#include <cstdlib>
using namespace std;
 
class Point{
private:
    int xpos, ypos;
public:
    Point(int x = 0 , int y = 0) : xpos(x), ypos(y) { }
    friend ostream& operator<< (ostream& os, const Point& pos);
};
 
ostream& operator<<(ostream& os, const Point& pos){
    os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;
    return os;
}
 
typedef Point * POINT_PTR;
 
class BoundCheckPointPtrArray{
private:
    POINT_PTR * arr;
    int arrlen;
 
    BoundCheckPointPtrArray(const BoundCheckPointPtrArray& arr){}
    BoundCheckPointPtrArray& operator= (const BoundCheckPointPtrArray& arr){}
 
public:
    BoundCheckPointPtrArray(int len) : arrlen(len){
        arr = new POINT_PTR[len];
    }
    POINT_PTR& operator[] (int idx){
        if(idx < 0 || idx >= arrlen){
            cout << "Array index out of bound exception" << endl;
            exit(1);
        }
        return arr[idx];
    }
    POINT_PTR operator[] (int idx) const{
        if(idx < 0 || idx >= arrlen){
            cout << "Array index out of bound exception" << endl;
            exit(1);
        }
        return arr[idx];
    }
    int GetArrLen() constreturn arrlen; }
    ~BoundCheckPointPtrArray() {delete []arr;}
};
 
int main(void){
    BoundCheckPointPtrArray arr(3);
    arr[0= new Point(34);
    arr[1= new Point(56);
    arr[2= new Point(78);
 
    for(int i = 0 ; i < arr.GetArrLen() ; i++)
        cout << *(arr[i]);
    delete arr[0];
    delete arr[1];
    delete arr[2];
    return 0;
}
cs

 

⭐️ StablePointPtrArray.cpp 실행결과

1
2
3
[3, 4]
[5, 6]
[7, 8]
cs

 

위 예제처럼 주소를 저장하는 방법이 더 많이 사용됩니다.

 

📜 출처

윤성우(2010). 윤성우 열혈 C++ 프로그래밍. 오렌지미디어.

Comments