캡슐화
엑셀을 밟으면 앞으로가고 브레이크를 밟으면 차가 멈추는것만 알아도 운전하는데 지장이 없다.
같은 원리로 다른사람이 만든 클래스나 헤더파일을 분석하거나 이해하려는 쓸데없는 짓은 하지말고 사용만 할줄알면된다.
정보 은폐의 목적은 몰라도되고 정보 은폐하는 방법을 익혀보도록하자.
#include <stdio.h>
class Time
{
private:
int hour, min, sec;
public:
Time(int h, int m, int s)
{
SetHour(h);
SetMinute(m);
sec = s;
}
int GetHour() { return hour; }
void SetHour(int h)
{
if (h >= 0 && h < 24)
{
hour = h;
}
}
int GetMinute() { return min; }
void SetMinute(int m)
{
if (m >= 0 && m < 60)
{
min = m;
}
}
int GetSecond() { return sec; }
void OutTime()
{
printf("현재 시간은 %d:%d:%d입니다.\n", hour, min, sec);
}
};
int main()
{
Time now(12, 34, 56);
now.SetHour(40); // 이상한 값 전달
now.OutTime(); // 전달이 잘 안되서 초기값이 출력(12)
now.SetHour(9); // 제대로된 값 전달
now.OutTime(); // 전달이 잘 되서 전달한 값 출력(9)
}

해당 예제에서 hour, min, sec 멤버 변수 모두 private로 선언해서 숨겼다.
외부에서 29시 86분 -12초 같은 말도 안되는 시간을 대입하는 것을 방지하기위해서이다.
주요 멤버를 숨기면 외부에서 값을 읽거나 변경할 수 없어서 멤버값을 대신 읽거나 쓰는 액세서를 제공한다.
액세서는 보통 Get, Set으로 시작하며 대상 멤버 변수를 대신 읽고 쓰는 역할을 한다.
hour 멤버에 대한 액세서는 GetHour, SetHour이다.
멤버값을 읽어주는 Get 함수는 통상 return문으로 멤버값을 읽어주지만 필요할 경우 둘 이상의 값을 조합하거나 실시간으로 값을 조사하여 돌려주기도 한다.
Set 함수는 멤버의 값을 변경한다.
무조건 대입하는 것이 아니라 조건에 맞는 값만 받아들여 객체 상태를 유효하게 관리한다.
SetHour(40)같은 이상한 값을 전달하면 무시하고 SetHour(9)같은 유효한 값만 받아들이듯이
멤버 특성에 맞는 고유한 규칙을 적용할 수 있어 안전하며 객체의 무결성이 유지된다.
프렌드
프렌드함수
클래스를 멋지게 만들었는데 멤버 변수에 접근할일이 생겼을때를 대비해서 비상장치가 있는것이라 보면된다.
객체의 신뢰성 향상과 기능 개선의 편의를 위해 정보 은폐는 꼭 필요한 기법이다.
그러나 때로는 너무 엄격한 은폐가 불편하거나 비효율적인 경우도 있다. 이럴때는 예외를 두어 특정 대상에 대해 모든 멤버를 공개할 수 있는데 이를 프렌드 지정이라고 한다.
프렌드로 지정되면 액세스 지정자에 상관없이 모든 멤버를 읽을 수 있다.
프렌드는 전역함수, 클래스, 멤버 함수의 세 가지 수준에서 지정한다.
외부의 전역 함수를 프렌드로 지정할때는 클래스 선언문 안에 원형을 밝히되 friend 지정자를 붙인다.
프렌드로 지정된 함수에는 클래스의 모든 멤버를 자유롭게 액세스 할 수 있는 특권이 주어진다.
#include <stdio.h>
class Date;
class Time
{
friend void OutToday(Date&, Time&);
private:
int hour, min, sec;
public:
Time(int h, int m, int s) { hour = h, min = m, sec = s; }
};
class Date
{
friend void OutToday(Date&, Time&);
private:
int year, month, day;
public:
Date(int y, int m, int d) { year = y, month = m, day = d; }
};
void OutToday(Date& d, Time& t)
{
printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d 입니다.\n",
d.year, d.month, d.day, t.hour, t.min, t.sec);
}
int main()
{
Date d(2018, 06, 29);
Time t(12, 34, 56);
OutToday(d, t);
}

Date 클래스는 날짜, Time 클래스는 시간을 표현한다.
주요 멤버가 모두 private 영역에 있기때문에 외부에서 읽을 수 없다.
날짜와 시간을 한번에 출력하려면 두 객체의 모든 멤버를 읽어야한다.
이럴때는 외부에 전역 함수를 정의하고 양쪽 클래스에 프렌드로 지정한다.
OutToday함수를 Time과 Date의 프렌드로 지정하여 양쪽 클래스에대해 자유이용권을 줄 수 있다.
외부함수지만 프렌드로 지정되었으니 마치 멤버 함수처럼 내부의 모든 멤버를 읽을 수 있다.
원칙을 따지자면 프렌드로 지정하는 것보다 각 클래스가 멤버를 읽어 주는 액세서 함수를 제공하는 것이 더 바람직하다.
그러나 멤버 수가 많아지면 일일이 액세서를 만들기도 번거로워 프렌드라는 방법을 제공한다.
프렌드 클래스
두 개의 클래스가 서로 밀접한 관계이고 상대편의 멤버를 참조해야 한다면 클래스를 통째로 프렌드로 지정한다.
클래스 선언문에 프렌드로 지정할 클래스의 이름을 밝혀서 사용할 수 있다.
위에서 했던 예제에서 OutToday 함수를 Date 클래스의 멤버함수로 선언하고 Date를 Time의 프렌드로 지정해보자.
#include <stdio.h>
class Time
{
friend class Date;
private:
int hour, min, sec;
public:
Time(int h, int m, int s) { hour = h, min = m, sec = s; }
};
class Date
{
private:
int year, month, day;
public:
Date(int y, int m, int d) { year = y, month = m, day = d; }
void OutToday(Time& t)
{
printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d 입니다.\n",
year, month, day, t.hour, t.min, t.sec);
}
};
int main()
{
Date d(2018, 06, 29);
Time t(12, 34, 56);
d.OutToday(t);
}

OutToday가 Date 소속으로 바뀌었다. 날짜 정보는 내부에 있으니 Date인수를 받을 필요 없고 Time객체만 받으면 된다.
Date가 Time의 프렌드로 지정되어 있어 Time 객체의 모든 멤버를 읽을 수 있다.
반대로 OutToday를 Time에 선언하고 Date가 Time을 프렌드로 선언해도 상관은 없다.
하지만 실제로클래스끼리 프렌드가 되는 예시는 흔하지는 않다.
프렌드 멤버함수
클래스끼리 프렌드가 되는 것은 양쪽의 멤버를 공유하는 편리한 방법이지만 허용 범위가 너무 넓어 위험하다.
상대편 멤버를 읽을 필요가 없는 함수까지도 권한을 가지게되어 실수할 가능성이 높아진다.
이럴때는 특정 멤버 함수에 대해서만 프렌드로 지정한다.
프렌드 멤버함수의 개념은 전역 함수와 같되 특정 클래스의 특정 멤버 함수에 대해서만 프렌드로 지정한다는 점이 다르다.
클래스 선언부에 프렌드로 지정할 멤버 함수의 소속과 원형을 friend 키워드와 함께 선언한다.
앞 예제의 OutToday 멤버 함수에게만 프렌드 지정을 해보자.
#include <stdio.h>
class Time;
class Date
{
private:
int year, month, day;
public:
Date(int y, int m, int d) { year = y, month = m, day = d; }
void OutToday(Time& t);
};
class Time
{
friend void Date::OutToday(Time&t);
private:
int hour, min, sec;
public:
Time(int h, int m, int s) { hour = h, min = m, sec = s; }
};
void Date::OutToday(Time& t)
{
printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d 입니다.\n",
year, month, day, t.hour, t.min, t.sec);
}
int main()
{
Date d(2018, 06, 29);
Time t(12, 34, 56);
d.OutToday(t);
}

Data::OutToday가 Time 객체를 인수로 받으려면 Time 클래스가 먼저 선언되어야한다.
그런데 Date::OutToday를 프렌드로 지정하려면 Date 클래스가 먼저 와야한다.
양쪽이 서로를 먼저 알아야 하는 상황이라 선언 순서로는 문제를 해결할 수 없다.
그래서 Date, Time 순으로 선언하되 선두에 Time에 대한 전방 선언을하여 클래스임을 미리 밝혔다.
OutToday의 본체에서 Time 클래스의 멤버를 참조하므로 이 함수의 본체를 Date 클래스 선언부에 둘 수 없고 Time 클래스 선언 후에 별도로 정의해야한다. 그래서 OutToday 함수는 두 클래스를 선언 한 후에 본체를 작성했다.
두 클래스가 서로 참조하는 상황이다보니 서로 알 수 있도록 미리 소개를 해준것으로 생각하면된다.
this
객체의 고유한 상태를 저장하는 멤버 변수는 객체별로 따로 유지하고 객체의 동작을 정의하는 멤버 함수는 공유한다.
속성은 객체마다 다르지만 동작은 공통적이어서 각 객체가 따로 가질 필요는 없다.
this 인수는 함수를 호출한 객체의 포인터이며 멤버를 참조하는 모든 문장 앞에 this-> 가 암시적으로 적용된다.
이런 식으로 동작하는 호출 규약을 thiscall이라고 하며 모든 멤버 함수에 강제로 적용된다.
멤버변수와 지역변수의 이름이 충돌할때 멤버변수임을 밝히기위해 this를 쓰는것이다.
// 파일이름 : this.cpp
#include <stdio.h>
class Simple
{
private:
int value;
public:
Simple(int avalue) : value(avalue) {}
void OutValue()
{
printf("value = %d\n", value);
}
};
int main()
{
Simple A(1), B(2);
A.OutValue();
B.OutValue();
}

정적 멤버 변수
정적 멤버 변수는 클래스 바깥에 선언되지만 클래스에 소속되며 객체별로 할당되지 않고 모든 객체가 공유된다.
// 파일이름 : ObjCount.cpp
#include <stdio.h>
int count = 0;
class Simple
{
private:
int value;
public:
Simple() { count++; }
~Simple() { count--; }
void OutCount()
{
printf("현재 객체 개수 : %d\n", count);
}
};
int main()
{
Simple s, * ps;
s.OutCount();
ps = new Simple;
ps->OutCount();
delete ps;
s.OutCount();
printf("크기 : %d\n", sizeof(s));
}

count 전역 변수를 0으로 초기화하고 Simple 클래스의 생성자에서 1 증가시키고 소멸자에서 1 감소시킨다.
정적이든 동적이든 생성된 객체의 생성자와 소멸자는 무조건 호출된다.
객체가 생성되거나 파괴될때 count 값의 변화로 생성된 객체의 수를 알 수 있다.
객체의 개수를 세는 목적은 달성했지만 전역 변수를 사용했다는 점에서 문제가 발생할 수 있다.
- 클래스와 관련된 정보가 외부에 선언되어있어 캡슐화 위반
- 전역 변수가 없으면 동작하지 않아 독립성이 떨어지며 재사용하기 어려움
- 전역 변수는 은폐할 수 없어 위험하다. 외부에서 count에 접근하면 방어할 수 없음
따라서 전역변수는 캡슐화, 정보은폐 등의 객체지향원칙과 맞지 않다.
그렇다면 객체 지향과 어울리지 않는 전역변수 count를 클래스 안에 선언해보자.
// 파일이름 : CountMember.cpp
#include <stdio.h>
class Simple
{
private:
int value;
int count = 0;
public:
Simple() { count++; }
~Simple() { count--; }
void OutCount()
{
printf("현재 객체 개수 : %d\n", count);
}
};
int main()
{
Simple s, * ps;
s.OutCount();
ps = new Simple;
ps->OutCount();
delete ps;
s.OutCount();
printf("크기 : %d\n", sizeof(s));
}

count를 Simple 클래스 안으로 포함시키고 0으로 초기화했다. 하지만 막상 실해해보면 객체별로 count 멤버가 있고 생성자에서 자신의 카운트만 개별적으로 증가시키므로 항상 1로 출력된다.
s나 ps나 각자의 count를 가지기때문에 메모리 낭비뿐만아니라 어느 객체의 count가 진짜 객체의 개수인지도 명확하지도 않다.
count는 개별 객체의 정보가 아니라 객체를 관리하는 정보이며 객체보다 상위의 클래스에 포함되어야한다.
이 문제를 해결하려면 count는 클래스의 멤버이면서 클래스의 모든 객체가 공유해야한다.
이때 사용하는 변수가 정적 멤버 변수(static 변수)이다.
#include <stdio.h>
class Simple
{
private:
int value;
static int count = 0;
public:
Simple() { count++; }
~Simple() { count--; }
void OutCount()
{
printf("현재 객체 개수 : %d\n", count);
}
};
int simple::count = 0;
int main()
{
Simple s, * ps;
s.OutCount();
ps = new Simple;
ps->OutCount();
delete ps;
s.OutCount();
printf("크기 : %d\n", sizeof(s));
}
count앞에 static 키워드를 붙여서 정적 멤버임을 명시해준다.
이 선언문은 count가 Simple 클래스 소속이라는 것만 알릴뿐 메모리는 할당하지 않는다.
정적 멤버 변수는 클래스 외부에 '::' 연산자와 함께 소속을 밝혀 별도로 정의하고 초기화한다.
클래스에 초기화를하려면 생성자로 호출했는데 정적변수의 초기화는 클래스 밖에서 이뤄지고있다.
static은 객체 내에서 존재하는 친구는 아니다. 클래스랑 별개라고 생각하면된다.
클래스를 통해 객체를 생성하면 그 객체는 static이라는 멤버 변수를 가지고있지않다.
정적멤버는 클래스 소속이므로 Simple::count 식으로 클래스명과 범위 연산자로 액세스 하는것이 원칙이다.
클래스명으로 참조하므로 객체가 전혀 생성되지 않은 상태에서도 참조할 수 있다.
정적 멤버 함수
정적 멤버 함수는 객체가 아닌 클래스와 연관되어 모든 객체에 공통적인 작업을 처리한다.
선언할때 함수 원형 앞에 static 키워드를 붙이며 외부에 작성할때는 static 키워드를 생략한다.
// 정적 멤버 함수
// 파일이름 : StaticFunc.cpp
#include <stdio.h>
class Simple
{
private:
int value;
static int count;
public:
Simple() { count++; }
~Simple() { count--; }
static void InitCount()
{
count = 0;
}
static void OutCount()
{
printf("현재 객체 개수 = %d\n", count);
}
};
int Simple::count;
int main()
{
Simple::InitCount();
Simple::OutCount();
Simple s, * ps;
Simple::OutCount();
ps = new Simple;
Simple::OutCount();
delete ps;
Simple::OutCount();
printf("크기 : %d\n", sizeof(s));
}

count의 초기 식을 생략하고 정적 멤버 함수 InitCount에서 0으로 초기화한다.
객체의 개수를 출력하는 OutCount 함수도 정적으로 선언했다. 객체의 개수는 클래스 전체와 연관되는 정보이다.
따라서 이 정보를 관리하는 함수도 정적으로 선언하는 것이 좋다.
두 함수 모두 객체가 생성되지않은 상태에서도 호출할 수도 있다.
main함수 맨위 코드에서 count를 초기화하고 출력했는데 아직 객체가 없어 0이 출력된다.
s 객체를 생성하면 카운트는 1이 되고 ps 객체를 동적 생성하면 2가 된다.
ps객체를 해제하면 1로 감소하고 main이 완전히 종료되어 s 객체가 파괴되면 0이 된다.
정적 멤버 함수는 객체에 의해 호출되는 것이 아니어서 호출 객체인 this는 전달되지 않는다.
클래스의 전반적인 작업을 하므로 호출 객체가 따로 없다.
그래서 정적 멤버 함수는 정적 멤버만 참조할 수 있으며 일반멤버(비정적멤버)는 액세스 할 수 없다.
보통 일반 멤버 앞에서는 암시적으로 this->가 자동으로 붙는다.
하지만 정적 멤버 함수는 this가 없어 본체에서 value를칭하면 누구의 value인지 구분할 수 없다.
정적 멤버 함수는 객체가 없어도 호출 가능한데 이때 value는 아예 존재하지도 않는다.
정적 멤버는 정적 멤버끼리만 어울린다.
셀프테스트) 회원이름, 전화번호, 주소, count를 멤버 변수로 갖는 class를 설계해보자.
count는 정적 멤버 변수로 작성 => 객체를 생성할때마다 자동으로 1씩 증가하고, getDate를 호출하면 저장했던 이름, 번호, 주소, count를 출력한다
// 파일이름 : classTest.cpp
/*
회원이름, 전화번호, 주소, count를 멤버변수로 갖는 class를 설계
count는 정적 멤버 변수로 작성=> 객체를 생성할때마다 자동으로 1증가
getdata를 호출하면 저장했던 이름, 번호, 주소, count 출력
*/
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string.h>
using namespace std;
class CMember
{
private:
char name[20];
char phone[15];
char addr[50];
static int count;
public:
// 문자열을 받을때는 원본이라서 const를 붙여줘야함.
CMember(const char* aname, const char* aphone, const char* aaddr)
{
strcpy(this->name, aname);
strcpy(this->phone, aphone);
strcpy(this->addr, aaddr);
count++;
}
~CMember()
{
cout << "소멸자 호출, 남아있는 회원 수: " << --count << endl;
}
static void InitCount()
{
count = 0;
}
void getData()
{
cout << "##### " << count << "번째 회원 #####" << endl;
cout << "이름 : " << this->name << endl;
cout << "번호 : " << this->phone << endl;
cout << "주소 : " << this->addr << endl;
cout << endl;
}
};
//static int CMember::count = 0;
int CMember::count;
int main()
{
CMember::InitCount();
CMember c1("홍길동", "010-1234-5678", "대한민국 어딘가");
c1.getData();
CMember c2("김길동", "010-1111-2222", "한반도 아무데나");
c2.getData();
return 0;
}

정적 멤버의 활용
정적 멤버는 선언과 정의가 분리되어 있고 클래스 외부에 정의하여 캡슐화에 위배되는 것처럼 보인다.
그러나 클래스와 관련된 정보를 표현하고 동작을 처리한다는 면에서 클래스 소속이 분명하며 액세스 지정자로 숨길 수 있다.
정적 멤버가 꼭 필요한 상황이 종종 있는데 유용하게 사용되는 예시를 살펴보자.
상수 멤버
상수 멤버는 값이 결정되면 변경할 수 없는 멤버이다. 클래스 전체에서 참조하는 중요한 값을 상수로 정의하는데 멤버 선언문 앞에 const 지정자를 붙인다.
// 파일이름 : ConstMember.cpp
#include <stdio.h>
class MathCalc
{
private:
const double pie; // const 멤버 변수 => 상수화 => 선언과 동시에 초기화
public:
MathCalc(double apie) : pie(apie) { } // 콜론 초기화
// pie가 const라서 생성자 내부에서 초기화할 수 없다.
// 초기화 되기전에 생성자에다가 콜론 초기화를 시켜주었음.
// 상수 멤버 변수를 초기화 시켜주는 것임
// 객체가 생성되는 형태
// 객체이름으로 메모리 할당 -> 생성자 호출, 초기화
void DoCalc(double r)
{
printf("반지름 %.2f인 원의 둘레 : %.2f\n", r, r * 2 * pie);
}
};
int main()
{
MathCalc m(3.1416);
m.DoCalc(5);
}

pie라는 명칭으로 실수형의 상수 멤버를 선언했다.
상수는 대입연산자를 쓸 수 없어 생성자의 초기화 리스트에서 초기화한다.
상수 멤버는 매크로 상수와 마찬가지로 값의 파악이 쉽고 일괄 수정이 용이하다는 이점이 있다.
각 객체가 상수 멤버를 따로 가지므로 객체별로 상수가 달라도 상관없으며 필요한 정밀도에 따라 각각 다른 원주율로 초기화할 수도 있다.
객체별로 다른 상수를 정의하는 경우도 있다. 다음 예제를 살펴보자.
// 파일이름 : ConstMemberInit.cpp
#include <stdio.h>
class Enemy
{
private:
const int Speed;
public:
Enemy(int aSpeed) : Speed(aSpeed) {}
void Move()
{
printf("%d의 속도로 움직인다.\n", Speed);
}
};
int main()
{
Enemy e1(10), e2(20);
e1.Move();
e2.Move();
}

Enemy 클래스는 적군을 표현하는데 객체별로 속도는 다르지만 한 번 속도가 정해지면 불변이다.
이럴때 상수 멤버를 사용하며 생성자의 초기화 리스트에서 객체별로 초기화한다.
객체별로 값이 달라지지 않는 고정된 상수라면 정적 상수 멤버로 선언하고 딱 한 번만 초기화하는 것이 유리하다.
(pie에 static const double pie 있는거 코드)
이때 pie 멤버 선언문에 static과 const를 같이 붙이면 공유 상수가 된다.
클래스에 포함되며 딱 한 카피만 존재하여 메모리가 절약되며 값을 바꿀 수도 없다.
정적 멤버는 생성자에서 초기화할 수 없고 클래스 외부에 정의하면서 초기화하는 것이 원칙적이다.
상수 멤버 함수
상수 멤버 함수는 객체의 상태를 읽기만 하는 함수이다.
멤버 변수를 읽기만 하고 변경하지 않는다면 const 지정자를 붙여 상수 함수로 선언한다.
함수 원형의 앞쪽은 리턴 타입을 지정하기 때문에 const 지정자를 함수명 뒤에 붙인다.
class Some
{
private:
int value;
public:
int SetValue(int avalue); // 비상수 멤버 함수
int GetValue() const; // 상수 멤버 함수
};
value의 값을 변경하는 SetValue는 상수 함수가 아니며 읽기만 하는 GetValue는 상수 함수이다.
그래서 GetValue 함수의 뒤에 const 지정자를 붙여 객체의 상태를 변경하지 않음을 분명히한다.
#include <stdio.h>
class Time
{
private:
int hour, min, sec;
public:
Time(int h, int m, int s)
{
SetTime(h, m, s);
}
void SetTime(int h, int m, int s)
{
hour = h;
min = m;
sec = s;
}
// outTime 멤버 함수가 상수화됨
void OutTime() const
{
printf("현재 시간은 %d:%d:%d 입니다.\n", hour, min, sec);
// hour = 24; // 오류
}
};
int main()
{
Time now(12, 34, 56);
now.SetTime(11, 22, 33);
now.OutTime();
const Time meeting(16, 00, 00); // 생성된 객체를 상수화 시킴
// meeting.SetTime(17, 00, 00); // 객체를 상수화시켰기때문에 SetTime으로 값을 대입하려고하면 오류뜸
meeting.OutTime();
}

SetTime 함수는 시간을 변경하므로 비상수 함수이다.
반면 OutTime 함수는 시간을 읽기만하고 변경하지 않아 상수 함수로 지정했다.
비상수로 선언된 now 객체는 SetTime으로 시간을 변경할 수 있고 OutTime으로 현재 시간을 출력할 수도 있다.
비상수 객체가 상수 함수를 호출하는 것은 언제나 가능하다.
meeting 객체는 선언할때 const 지정자로 상수 객체임을 명시하여 값을 변경할 수 없다.
상수 객체는 상수 함수만 호출할 수 있으며 상태를 변경하는 비상수 함수는 호출할 수 없다.
그래서 meeting 객체는 OutTime으로 시간을 확인할 수만 있고 SetTime으로 시간을 변경할 수는 없다.
함수의 상수성을 지정하는 const 지정자는 함수가 암시적으로 전달받는 this의 상수성을 결정한다.
SetTime의 this는 Time * const 타입이며 this 자체는 상수지만 this가 가리키는 대상체인 객체는 상수가 아니어서 멤버값을 변경할 수 있다.
반면 OutTime의 this는 const Time * const 타입이며 this도 상수이고 대상체는 객체도 상수여서 멤버값을 변경할 수 없다.
상수 객체는 모든 멤버가 상수로 취급되어 상태를 변경할 가능성이 있는 비상수 함수를 호출할 수 없다.
상수 객체에 대해 호출 가능한 함수는 const 지정자를 붙여 객체의 상태를 변경하지 않는다는 것을 분명히 표시해야한다. 코드에서 멤버값을 바꾸지 않더라도 컴파일러가 코드까지 다 점검할 수는 없으므로 const 지정자가 없으면 비상수 함수로 취급한다.
mutable
상수 함수나 상수 객체는 멤버를 읽을 수만 있고 변경할 수는 없다. mutable 지정자는 이 규칙에대한 예외를 지정하여 이 속성을 가지는 멤버는 객체의 상수성과 상관없이 언제나 수정이 가능하다.
객체의 상태를 저장하지 않는 임시 멤버에 대해 이 속성을 사용한다.
// mutable.cpp
#include <stdio.h>
class Some
{
private:
mutable int temp;
public:
Some() {}
void method() const { temp = 0; }
};
int main()
{
Some s;
s.method();
const Some t;
t.method();
}
임시 정보를 저장하는 temp 멤버 함수는 중요한 정보가 아니어서 mutable로 지정하였다.
method는 상수 함수로 선언되었지만 temp의 값을 마음대로 변경할 수 있다.
main의 t 객체는 상수로 선언되었지만 method를 호출하여 temp의 값을 변경한다.
변수는 원래 값을 마음대로 변경할 수 있지만 const 지정자는 값 변경을 금지한다.
mutable 지정자는 이런 const의 값 변경 금지 기능을 뒤집어 언제든지 값을 변경하도록 허락한다.
객체의 상수성을 완전 무시해버리는 것이다.
const는 우발적인 변경을 방지하여 안전성을 높이기 위해 도입되었다.
그러나 객체의 일부이면서 객체의 속성이 아닌 멤버도 있다. 값 교환을 위한 임시 변수나 잠시 사용할 버퍼, 디버깅 정보 등은 객체의 상태라고 볼 수 없으며 이런 값은 언제 바뀌어도 상관없다.
객체의 상태가 아닌 멤버는 mutable로 지정하여 제외한다.
const는 객체의 안정성 향상을 위해 일정한 역할을 담당하지만 임시적인 정보나 디버깅에는 불편한 면이 있다.
이를 해소하기 위해 도입한 키워드가 mutable이다.
상수성을 정확히 결정하여 const 지정자를 일일이 붙이고 관리하는 것은 번거롭다.
하지만 처음부터 원칙대로 상수 지정을 제대로해두면 우연한 실수를 방지하여 안정성이 높아진다.
상수화를 굳이 시켜놨는데 바꾸고싶은 경우가 딱히 없을것이기때문에 이런게 있다. 라고 기억만 해두도록 하자.