도순씨의 코딩일지
C++ :: 디폴트 대입 연산자, 배열의 인덱스 연산자 오버로딩, const 함수 오버로딩 본문
🌼 디폴트 대입 연산자
대입 연산자의 대표적인 특성은 다음과 같습니다.
💡 정의하지 않으면 디폴트 대입 연산자가 삽입된다.
💡 디폴트 대입 연산자는 멤버 대 멤버의 복사(얕은 복사)를 진행한다.
💡 정의하지 않으면 디폴트 대입 연산자가 삽입된다.
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 = 0, int 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(111, 222);
First fcpy;
Second ssrc(333, 444);
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 = 0, int 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(111, 222, 333, 444);
Second scpy(0, 0, 0, 0);
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 = 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;
}
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(3, 4);
arr[1] = Point(5, 6);
arr[2] = Point(7, 8);
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() const{ return arrlen; }
~BoundCheckPointPtrArray() {delete []arr;}
};
int main(void){
BoundCheckPointPtrArray arr(3);
arr[0] = new Point(3, 4);
arr[1] = new Point(5, 6);
arr[2] = new Point(7, 8);
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++ 프로그래밍. 오렌지미디어.
'𝐏𝐑𝐎𝐆𝐑𝐀𝐌𝐌𝐈𝐍𝐆 > 𝐂++' 카테고리의 다른 글
C++ :: 템플릿의 특수화, 인자, 매개변수, 디폴트값 (0) | 2020.09.01 |
---|---|
C++ :: 템플릿(Template), 클래스 템플릿(class Template) (0) | 2020.08.31 |
C++ :: 연산자 오버라이딩, 단항 연산자의 오버로딩, 전위증가와 후위증가 오버로딩 (0) | 2020.08.29 |
C++ :: 멤버함수와 가상함수의 동작원리 (0) | 2020.08.28 |
C++ :: 객체 포인터의 참고관계, 가상함수, 순수 가상함수, 가상 소멸자 (0) | 2020.08.27 |