Const
const 키워드는 해당 변수를 상수로 만들어준다. (상수는 선언과 동시에 초기화를 해줘야하며 변경이 불가능하다.)
어떤 값(객체의 내용)이 불변이어야 한다는 제작자의 의도를 컴파일러 및 다른 프로그래머와 나눌 수 있는 수단.
- 클래스 외부에서는 전역 혹은 네임스페이스 유효 범위의 상수를 선언하는데 사용 가능.
- 파일, 함수, 블록 유효 범위에서 static으로 선언한 객체에도 const를 붙일 수 있음.
- 클래스 내부의 경우에는, 정적 멤버 및 비정적 데이터 멤버 모두를 상수로 선언 가능.
- 포인터에도 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 |
char greeting[] = "Hello";
char greeting2[] = "Nice";
char* p = greeting;
//쉽게 생각해서 const가 * 왼쪽에 있으면 const char*를 한 묶음으로 보면된다.
//*은 해당 포인터가 가리키고 있는 주소값의 Value니까 (여기서는 *p는 'H')
//결론적으로 p가 가리키고 있는 주소값의 Value를 변경하지 못한다고 생각하면 된다.
const char* p1 = greeting;
p1 = greeting2; //가능.
*(p1) = 'S'; //Error.
*(p1+1) = 'C'; //Error.
//const가 * 오른쪽에 있으면 const p를 한묶으로 보면 된다.
//p는 문자 'H'의 주소값이니까 p의 주소값을 변경하지 못한다고 생각하면 된다.
//즉, p가 다른 주소값을 가리키도록 하지 못하므로
char* const p2 = greeting;
p2 = greeting2; //Error.
*(p2) = 'S'; //가능.
*(p2+1) = 'C'; //가능.
//위에 두가지 case 모두 불가능.
const char* const p3 = greeting;
|
cs |
const (함수)
1. 함수 반환에 const.
- 안전성이나 효율을 포기하지 않고도 사용자측의 에러 돌발 상황을 줄이는 효과를 볼 수 있다.
- 예를 들어, 연산자 오버로딩 사용 시 if ((a*b) == c)를 if ((a*b) = c)와 같은 실수를 방지할 수 있다.
2. 함수의 매개 변수에 const
- 함수 내에서 객체를 수정할 일이 없으면 무조건 const로 선언하라.
3. 멤버 함수 뒤에 const
- "해당 멤버 함수가 상수 객체에 대해 호출될 함수이다"라는 사실을 알려주는 것이다.
- 이 기능이 중요한 이유
1) 클래스의 인터페이스를 이해하기 좋게 하기 위함.
2) 상수 객체에서만 호출할 수 있도록 하기 위함.
(c++ 프로그램의 실행 성능을 높이는 핵심 기법 중 하나는 객체 전달을 '상수 객체에 대한 참조자'로 전달하는 것.)
(전달된 '상수 객체'에서 사용할 멤버 함수가 있어야 하기 때문이다.)
- const 키워드가 있고 없고의 차이만으로도 함수 오버로딩이 가능하다.
const (객체)
const 객체는 객체의 멤버 변수를 어떠한 경우에도 수정할 수 없으며 const 멤버 함수만 호출 할 수 있다.
위에서 말했 듯이 상수 객체에 대한 참조자로 전달할 때 프로그램 실행 성능이 높아진다.
비트수준 상수성
- 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야(정적 멤버 제외) 그 멤버 함수가 'const'임을 인정하는 개념.
- C++에서 정의하고 있는 상수성이 비트 수준 상수성.
- 그러나 하기와 같이 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
31 |
class CMyString
{
public:
CMyString(char* pString)
{
int nLength = strlen(pString);
m_pString = new char[nLength];
strcpy(m_pString, pString);
}
private:
char* m_pString;
public:
char& operator[](int nIndex) const
{
return m_pString[nIndex];
}
};
int _tmain(int argc, _TCHAR* argv[])
{
const CMyString cstrVal("Hello");
char* pVal = &cstrVal[0];
*pVal = 'J'; //문자열이 "Jello"으로 변경.
} |
cs |
- 위와 같은 상황이 발생하지 않기 위해서는 반환형으로 const char&를 해줘야함.
논리적 상수성
- 비트수준 상수성을 보완하는 대체 개념.
- 컴파일러는 비트수준 상수성을 기준으로 동작하기 때문에 프로그래머는 논리적 상수성을 기준으로 프로그램을
작성해야 한다.
- 상수 멤버 함수라고 해서 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트 정도는 바꿀순 있되,
그것을 사용자 측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다는 것.
- 상수 객체지만 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
28
29 |
class CMyString
{
public:
CMyString(char* pString)
{
m_nLength = strlen(pString);
m_pString = new char[m_nLength];
strcpy(m_pString, pString);
}
private:
char* m_pString;
mutable int m_nLength; //mutable 키워드로 멤버 변수 선언.
public:
const char& operator[](int nIndex) const
{
return m_pString[nIndex];
}
};
int _tmain(int argc, _TCHAR* argv[])
{
const CMyString cstrVal("Hello");
return 0;
} |
cs |
코드 중복 피하기
만약 위와 같은 코드에서 operator[] 함수 내에 코드가 엄청나게 길었다고 가정해보자.
상수 객체는 반드시 const char& operator[](int nIndex) const를 호출해야 하고
비상수 객체는 char& operator[](int nIndex) 함수를 호출하게 될 것이므로 두 함수는 모두 있어야 한다.
그런데 만약 이 두 함수의 내용이 같다면, 이러한 함수가 여러 개라면 코드의 크기는 엄청나게 될 것이다.
(컴파일 시간, 유지보수, 코드 크기 등 문제 발생..)
그렇다고 반환값을 통일시켜버리면 위의 비트수준 상수성 예시처럼 const의 안정성이 지켜지지 않는다.
해결 방법.
- 두 함수의 차이점은 단지 반환 타입에 const가 붙어 있냐 없냐만 차이가 있다. ( const char&, char& )
- 비상수 operator[] version 함수에서 상수 operator[] 함수를 호출하는 것!! (casting을 이용)
(상수 함수에서 비상수 함수를 호출하는 것은 애초에 비상수 함수에서 무슨 값을 변경할지 모르기 때문에
안정성이 보장되지 않는다.)
- 실제 동작 소스 코드는 상수 operator[] 함수 내에 있는다. (위랑 같은 이유)
(상수, 비상수 두개 모두 사용하는 것이므로 소스 코드 변경 사항이 없어야하므로!!)
- 캐스팅은 일반적으로 좋지 않은 생각이지만.. 많은 양의 코드 중복도 보통 문제가 아님.
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 |
class CMyString
{
public:
CMyString(char* pString)
{
m_nLength = strlen(pString);
m_pString = new char[m_nLength];
strcpy(m_pString, pString);
}
private:
char* m_pString;
mutable int m_nLength; //mutable 키워드로 멤버 변수 선언.
public:
//상수 version operator[]
const char& operator[](int nIndex) const
{
//엄청난 크기의 코드가 들어있다고 가정.
return m_pString[nIndex];
}
//비상수 version operator[]
char& operator[](int nIndex)
{
return
const_cast<char&>(
static_cast<const CMyString&>
(*this)[nIndex]
);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
const CMyString cstrVal("Hello");
CMyString strVal("Nice");
cstrVal[0] = 'R'; //Error C3892
strVal[0] = 'F'; //가능
return 0;
} |
cs |
1) 상수 operator[] 함수에 코드를 작성한다.
- 상수 객체는 상수 operator[] 함수를 그대로 사용하게 되므로 문제 없다.
2-1) 비상수 operator[] 함수에서는 캐스팅을 통해 상수 operator[]를 호출한다.
2-2) 상수 operator[]로부터 받은 상수형 인자를 비상수 형으로 변환하여 최종 반환한다.
- 애초에 상수 버젼 함수에 소스 코드가 들어가져 있으므로 안정성 보장.
- 그러나 만약 반대의 경우라면 코드 소스가 비상수 함수에 들어가게 되고 이는 코드의 변경이 가능해지기 때문에
안정성이 보장되지 않는다. (애초에 비트수준 상수성에 위배되기도 함.)
- 그리고 상수 함수를 비상수 함수로 바꾸게 되면 모든 재앙의 씨앗이 되므로 절대로 해선 안된다. (중요)
2-3) 소스 검토
- static_cast<const CMyString&>(*this)[nIndex] (비상수 객체에서 상수 객체로 변환)
· 만약 그냥 operator[]를 사용했으면 재귀함수에 빠지게 되므로 casting을 통해 상수 operator[]를 호출한다.
· const를 붙이는 캐스팅은 안전한 타입 변환이므로 static_cast만 써도 좋다.
· 변환 형을 static_cast<const CMyString> 으로만 한다면 상수형 operator[] 함수를 호출하는 객체는 strVal 객체가 아닌
임시 객체가 되버리므로 상수형 operator[] 함수에서 동작되는 부분들은 실제 객체와 아무 연관이 없어져버리게 된다.
그러므로 반드시 형변환시에는 const CMyString& 참조형으로 변환해야만 한다.
3줄 요약
- const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는데 도움을 준다.
- 컴파일러는 비트수준 상수성만 지키기 때문에, 프로그래머는 논리적 상수성을 사용해서 코드를 작성해야 한다.
- 상수 멤버 및 비상수 멤버 함수가 동일한 코드라면 코드 중복을 피하기 위해, 비상수 버젼이 상수 버젼을 호출하도록
만드는게 좋다.
'Program Language > C++' 카테고리의 다른 글
항목7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자. (0) | 2017.07.21 |
---|---|
항목6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자. (0) | 2017.07.19 |
항목5. c++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자. (0) | 2017.07.13 |
항목4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자. (0) | 2017.07.05 |
항목2. #define을 쓰려거든 const, enum, inline을 떠올리자. (0) | 2017.06.12 |