Const Function (상수 함수)


1. 상수 함수

const를 붙이는 함수들 (ex. void FOO() const)
상수 함수 안에서는 멤버 값을 변경할 수 없다.
const는 반드시 사용해야 한다.
상수 인스턴스는 상수 함수만 호출가능하다.

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
#include <iostream>

using namespace std;

class Point {
public:
int x, y;

Point(int a = 0, int b = 0) : x(a), y(b) { }

void set(int a, int b) {
x = a;
y = b;
}

void print() const {
x = 10; //error, 상수 함수 안에서는 멤버 값을 변경할 수 없다.
cout << x << ", " << y << endl;
}
};

int main() {
const Point p(0, 0);

p.x = 10; // error : 상수 인스턴스 이기때문이다.
p.set(10, 10); // error : 객체를 상수화를 하였기 때문에 상수함수만 가능하다.
// 위에 2가지는 불가능 하더라고 밑에 print는 가능 해야한다. 이럴때 필요한것이 const이다
p.print();
return 0;
}

2. 상수 함수 호출

아무리 상수객체라고 하더라고 호출이 가능하도록 열어 둬야 한다.
핵심 : 상수 함수는 필수이다. 객체의 상태를 변경하지 않은 모든 멤버 함수는 반드시 상수함수로 만들어야 한다.
void foo(const Rect& r) 코드에서 r에 대해서 면적은 구할수 있어야 한다!!

C++ 기본 문법 : call by value 대신 const & 가 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

using namespace std;

class Rect {
int x, y, w, h;
public:
int getArea() const { return w * h; }
};

//C++ 기본 문법 : call by value 대신 const & 가 좋다.
void foo(const Rect &r) {
int n = r.getArea();
}

int main() {
Rect r; //초기화 하였다고 가정하고

int n = r.getArea(); // ok

foo(r);
}

3. 논리적 상수성

상수에서의 문제점을 해결하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

using namespace std;

class Point {
int x, y;
public :
Point(int a = 0, int b = 0) : x(a), y(b) { }

//객체의 상태를 문자열로 반환하는 함수 : java, C#에 있는 개념
char *toString() {
char cache[32];
sprintf(cache, "%d, %d", x, y);
return cache;
}
};

int main() {
Point p(1, 2);
cout << p.toString() << endl;
cout << p.toString() << endl;
return 0;
}

위와 같이 했을 때는 매 번 cache를 생성해서 작성하게 된다.
이 부분에 대해서 불필요 하다고 생각 되기 때문에 다음과 같이 수정을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
int x, y;
char cache[32];
bool cache_valid;
public :
Point(int a = 0, int b = 0) : x(a), y(b), cache_valid(false) { }
char *toString() {
if (cache_valid == false) {
sprintf(cache, "%d, %d", x, y);
cache_valid = true;
}
return cache;
}
};

위와 같이 수정하였을 경우 cache에 대해서 매번 생성하는 불필요한 행위는 안 할 수있으나 문제가 있다.

실제로 멤버 x, y의 값은 변경하지는 않지만 cache_vaild를 수정하기 때문에 const를 붙일수가 없다.
논리적으로 봤을 때 cache와 cache_valid는 직접적으로 영향을 끼치는 역활이 아니라 도우미 역활이기때문에 const를 붙이는 것이 맞다.
이런한 점을 해결하기 위한 2가지 방법을 살펴보자.

4. 논리적 상수성 해결법

1. 변하는 멤버는 mutable로!!

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
#include <iostream>

using namespace std;

class Point {
int x, y;
mutable char cache[32];
mutable bool cache_valid; // 상수 함수에서도 변경 가능한 멤버 data

public :
Point(int a = 0, int b = 0) : x(a), y(b), cache_valid(false) { }

char *toString() const {
if (cache_valid == false) {
sprintf(cache, "%d, %d", x, y);
cache_valid = true;
}
return cache;
}
};

int main() {
Point p(1, 2);
cout << p.toString() << endl;
cout << p.toString() << endl;
return 0;
}

4. 논리적 상수성 해결법

외부에서 바라볼때는 상수 함수가 되야 하지만 내부적으로는 멤버 변수의 값을 변경해야 하는 문제

2. 변하지 않은 것과 변하는 것은 분리되어야 한다.

상수 함수에서 변해야 하는 것이 있다면 별도의 구조체로 분리한다.

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
#include <iostream>

using namespace std;
struct Cache {
char data[32];
bool valid;
};

class Point {
int x, y;
Cache *pCache;

public :
Point(int a = 0, int b = 0) : x(a), y(b) {
pCache = new Cache;
pCache->valid = false;
}

char *toString() const {
if (pCache->valid == false) {
sprintf(pCache->data, "%d, %d", x, y);
pCache->valid = true;
}
return pCache->data;
}

~Point() {
delete pCache;
}
};

int main() {
Point p(1, 2);
cout << p.toString() << endl;
cout << p.toString() << endl;
return 0;
}