|
const 객체와 const 객체의 특성들
다음과 같이 변수를 상수화 하듯이, const int num=10; 다음과 같이 객체를 상수화할 수 있습니다. const SoSimple sim(20); 이 객체를 대상으로는 const 멤버함수만 호출이 가능하다. 이는 객체의 const 선언이 다음의 의미를 갖기 때문입니다. 이 객체의 데이터 변경을 허용하지 않겠습니다. 때문에 const 멤버함수의 호출만 허용하는 것입니다. 변경시킬 능력이 있는 함수는 아예 호출을 허용하지 않는 것입니다.
#include <iostream> using namespace std; class SoSimple { private: int num; public: SoSimple(int n) : num(n) { } SoSimple& AddNum(int n) { num += n; return *this; } void ShowData()const { cout << "numL " << num << endl; } }; int main(void) { const SoSimple obj(7); obj.ShowData(); return 0; } |
const 객체를 생성하였습니다. 멤버함수는 const 함수이기 때문에 const 객체를 대상으로 호출이 가능합니다. 멤버변수에 저장된 값을 수정하지 않는 함수는 가급적 const로 선언해서, const 객체에서도 호출이 가능하도록 할 필요가 있습니다.
const와 함수 오버로딩
함수의 오버로딩이 성립하려면 매개변수의 수나 자료형이 달라야 합니다. 다음과 같이 const의 선언유무도 함수 오버로딩의 조건에 해당이 됩니다.
#include <iostream> using namespace std; class SoSimple { private: int num; public: SoSimple(int n) : num(n) { } SoSimple& AddNum(int n) { num += n; return *this; } void SimpleFunc() { cout << "SimpleFunc: " << num << endl; } void SimpleFunc() const { cout << "const SimpleFunc: " << num << endl; } }; void YourFunc(const SoSimple &obj) { obj.SimpleFunc(); } int main(void) { SoSimple obj1(2); const SoSimple obj2(7); obj1.SimpleFunc(); obj2.SimpleFunc(); YourFunc(obj1); YourFunc(obj2); return 0; } |
일반 객체와 const 객첼르 각각 생성하고 있습니다. 일반 객체를 대상으로 함수를 호출하면 일반 멤버함수가, const 객체를 대상으로 함수를 호출하면 const 멤버함수가 호출됩니다. YourFunc 함수는 전달되는 인자를 참조자로, const 참조자로 받습니다. 그것도 const 참조자로 받습니다. 따라서 참조자를 이용한 28행의 함수호출의 결과로 20행의 const 멤버함수가 호출됩니다. 오버로딩 된 const 함수가 호출되는 상황을 보이기 위한 목적으로 간단히 작성되었습니다. 실행결과만 보더라도 const 함수가 호출되는 상황을 파악할 수 있습니다.
클래스와 함수에 대한 friend 선언
클래스의 friend 선언. A클래스가 B클래스를 대상으로 friend 선언을 하면, B 클래스는 A 클래스의 private 멤버에 직접 접근이 가능합니다. 단, A 클래스도 B 클래스의 private 멤버에 직접 접근이 가능 하려면, B 클래스가 A 클래스를 대상으로 friend 선언을 해줘야 합니다.
#pragma warning(disable:4996) #include <iostream> #include <cstring> using namespace std; class Girl; class Boy { private: int height; friend class Girl; public: Boy(int len) : height(len) { } void ShowYourFriendInfo(Girl &frn); }; class Girl { private: char phNum[20]; public: Girl(char * num) { strcpy(phNum, num); } void ShowYourFriendInfo(Boy *frn); friend class Boy; }; void Boy::ShowYourFriendInfo(Girl &frn) { cout << "Her phone number: " << frn.phNum << endl; } void Girl::ShowYourFriendInfo(Boy &frn) { cout << "His height : " << frn.height << endl; } int main(void) { Boy boy(170); Girl girl("010-1234-5678"); boy.ShowYourFriendInfo(girl); girl.ShowYourFriendInfo(boy); return 0; } |
정의가 뒤에 나오는 함수의 호출을 위해서 함수의 원형을 선언하듯이, 이렇게 클래스도 선언이 가능합니다. 이는 클래스의 이름을 알리는 것입니다. private 영역에도 friend 선언이 가능함을 보이기 위해서 이곳에 friend 선언을 삽입하였습니다. 아직 정의되지 않은 Girdl 이라는 클래스의 이름이 등장합니다. 그럼에도 불구하고 컴파일이 가능한 이유는 앞서 5행에서 Girl이 클래스의 이름임을 알렸기 때문입니다.
friend 선언의 방법과 그 의미는 소스코드상에서 충분히 파악이 가능하기 때문에, 클래스의 선언과 정의도는 위치에 대해서 설명하였습니다. Girl은 클래스의 이름입니다. 그리고 바로 그 Girl 클래스를 friend로 선언합니다.
friend 선언은 언제?
friend 선언은 객체지향의 대명사 중 하나인 정보은닉을 무너뜨리는 문법이기 때문입니다. 필요한 상황에서 극히 소극적으로 사용해야 합니다.
함수의 friend 선언
전역 함수를 대상으로도, 클래스의 멤버함수를 대상으로도 friend 선언이 가능합니다. friend로 선언된 함수는 자신이 선언된 클래스의 private 영역에 접근이 가능합니다.
#include <iostream> using namespace std; class Point; class PointOP { private: int opcnt; public: PointOP() : opcnt(0) { } Point PointAdd(const Point&, const Point&); Point PointSub(const Point&, const Point&); ~PointOP() { cout << "Operation times: " << opcnt << endl; } }; class Point { private: int x; int y; public: Point(const int &xpos, const int &ypos) : x(xpos), y(ypos) { } friend Point PointOP::PointAdd(const Point&, const Point&); friend Point PointOP::PointSub(const Point&, const Point&); friend void ShowPointPos(const Point&); }; Point PointOP::PointAdd(const Point& pnt1, const Point& pnt2) { opcnt++; return Point(pnt1.x + pnt2.x, pnt1.y + pnt2.y); } Point PointOP::PointSub(const Point& pnt1, const Point& pnt2) { opcnt++; return Point(pnt1.x - pnt2.x, pnt1.y - pnt2.y); } int main(void) { Point pos1(1, 2); Point pos2(2, 4); PointOP op; ShowPointPos(op.PointAdd(pos1, pos2)); ShowPointPos(op.PointSub(pos1, pos2)); return 0; } void ShowPointPos(const Point& pos) { cout << "x: " << pos.x << ", "; cout << "y: " << pos.y << endl; } |
컴파일을 하기 위해서는 Point 가 클래스의 이름임을 컴파일러에게 알려줘야 합니다. Point 클래스는 뒤에서 등장하기 때문에 이렇게 별도로 Point가 클래스의 이름임을 선언해야 합니다. ShowPointPos 함수도 Point 클래스의 friend 로 선언되었기 때문에 private 멤버에 접근이 가능합니다. friend 선언을 위해서 별도의 함수원형을 선언할 필요는 없습니다.
C++에서의 static
static 멤버변수와 static 멤버 함수를 소개하기에 앞서, C언어에서 공부한 static의 개념을 정리해보겠습니다. 전역변수에 선언된 static의 의미는 선언된 파일 내에서만 참조를 허용하겠다는 의미입니다. 함수 내에 선언된 static의 의미는 한번만 초기화되고, 지역변수와 달리 함수를 빠져나가도 소멸되지 않는다는 의미입니다.
#include <iostream> using namespace std; void Counter() { static int cnt; cnt++; cout << "Current cnt: " << cnt << endl; } int main(void) { for (int i = 0; i < 10; i++) Counter(); return 0; }
|
static 변수는 전역변수와 마찬가지로 초기화하지 않으면 0으로 초기화됩니다.
전역변수가 필요한 상황
#pragma warning(disable:4996) #include <iostream> using namespace std; int simObjCnt = 0; int cmxObjCnt = 0; class SoSimple { public: SoSimple() { simObjCnt++; cout << simObjCnt << "th SoSimple object" << endl; } }; class SoComplex { public: SoComplex() { cmxObjCnt++; cout << cmxObjCnt << "th SoComplex Object" << endl; } SoComplex(SoComplex ©) { cmxObjCnt++; cout << cmxObjCnt << "th SoComplex Objet" << endl; } }; int main(void) { SoSimple sim1; SoSimple sim2; SoComplex com1; SoComplex com2 = com1; SoComplex(); return 0; } |
simObjCnt는 SoSimple 객체들이 공유하는 변수입니다. cmxObjCnt는 SoComplex 객체들이 공유하는 변수입니다. 이 둘은 모두 전역변수이기 때문에 이러한 제한을 지켜 줄만한 아무런 장치도 존재하지 않습니다.
static 멤버변수(클래스 변수)
static 멤버변수는 클래스 변수라고도 합니다.
#pragma warning(disable:4996) #include <iostream> using namespace std; class SoSimple { private: static int simObjCnt; public: SoSimple() { simObjCnt++; cout << simObjCnt << "th SoSimple object" << endl; } }; int SoSimple::simObjCnt = 0; class SoComplex { private: static int cmxObjCnt; public: SoComplex() { cmxObjCnt++; cout << cmxObjCnt << "th SoComplex Object" << endl; } SoComplex(SoComplex ©) { cmxObjCnt++; cout << cmxObjCnt << "th SoComplex Object" << endl; } }; int SoComplex::cmxObjCnt = 0; int main(void) { SoSimple sim1; SoSimple sim2; SoComplex cmx1; SoComplex cmx2 = cmx1; SoComplex(); return 0; } |
static 멤버변수의 초기화 문법은 다음과 같이 별도로 정의되어 있습니다. int SoSimple::simObjCnt=0; SoSimple 클래스의 static 멤버변수 simObjCnt가 메모리 공간에 저장될 때 0으로 초기화하라는 뜻입니다. 이제 static 멤버변수의 초기화를 별도의 방식으로 진행하는 이유가 이해 되었나요?
static 멤버변수의 또 다른 접근방법
static 멤버변수는 어디서든 접근이 가능한 변수입니다.
#include <iostream> using namespace std; class SoSimple { public: static int simObjCnt; public: SoSimple() { simObjCnt++; } }; int SoSimple::simObjCnt = 0; int main(void) { cout << SoSimple::simObjCnt << "th SoSimpe Object" << endl; SoSimple sim1; SoSimple sim2; cout << SoSimple::simObjCnt << "th SoSimple Object" << endl; cout << sim1.simObjCnt << "th SoSimple Object" << endl; cout << sim2.simObjCnt << "th SoSimple Object" << endl; return 0; } |
static 멤버변수가 public 으로 선언되었습니다. static 멤버변수는 항상 초기화르 진행합니다. public 으로 선언된 static 멤버는 이런 식으로 어디서든 접근이 가능합니다. 객체의 멤버가 아닌데, 어떻게 멤버변수에 접근을 하겠는가? 객체 생성 이전에도 호출이 가능하다. 그런데 어떻게 멤버변수에 접근이 가능하겠는가? 멤버변수에 접근을 한다고 치자. 그렇다면 어떤 객체으 멤버변수에 접근을 해야겠는가? static 멤버함수 내에서는 static 멤버변수와 static 멤버함수만 호출이 가능합니다.
const static 멤버
#include <iostream> using namespace std; class CountryArea { public: const static int RUSSIA = 1707540; const static int CANADA = 998467; const static int CHINA = 957290; const static int SOUTH_KOREA = 9922; }; int main(void) { cout << "russia area: " << CountryArea::RUSSIA << "km^2" << endl; cout << "canada area: " << CountryArea::CANADA << "km^2" << endl; cout << "china area: " << CountryArea::CHINA << "km^2" << endl;
return 0; } |
국가별 면적의 크기를 저장해 놓은 클래스입니다. 이렇듯 const static 상수는 하나의 크래스에 둘 이상 모이는 것이 보통입니다. 7~10행에 정이된 상수에 접근하기 위해서 굳이 객체를 생성할 필요는 없습니다. 이렇듯 클래스의 이름을 통해서 접근하는 것이 편하기도 하고, 접근하는 대상에 대한 정보를 쉽게 노출하는 방법이 되기도 합니다.
키워드 mutable
const 함수 내에서의 값의 변경을 예외적으로 허용합니다.
#pragma warning(disable:4996) #include <iostream> using namespace std; class SoSimple { private: int num1; mutable int num2; public: SoSimple(int n1,int n2) : num1(n1), num2(n2) { } void ShowSimpleData() const { cout << num1 << ", " << num2 << endl; } void CopyToNum2() const { num2 = num1; } }; int main(void) { SoSimple sm(1, 2); sm.ShowSimpleData(); sm.CopyToNum2(); sm.ShowSimpleData(); return 0; } |
num2가 mutable로 선언되었습니다. 때문에 이 변수는 const 함수 내에서의 변경이 허용됩니다. const 함수 내에서 num2에 저장된 값을 변경하고 있습니다. 이는 num2가 mutable로 선언되었기 때문에 가능합니다. num2가 mutable로 선언되고, CopyTonum2 함수가 const로 선언되었으니, 이 함수내에서 실수로 num1의 값이 변경되는 일은 발생하지 않겠구나.