본문 바로가기
코스웨어/16년 스마트컨트롤러

2016-09-29_조재찬_스터디일지_CPP-const, friend, static, mutable 선언

by 알 수 없는 사용자 2016. 9. 29.
728x90
반응형

const 객체와 const 객체의 특성들

 

변수를 상수화 하듯이, 객체도 상수화 가능하다.

const SoSimple sim(20);

 

객체에 const 선언이 붙으면, 이 객체를 대상으로는 const 멤버함수만 호출이 가능하다.

(객체의 데이터 변경을 허용하지 않음)

 

아래 예제에서 const객체 sim의 멤버 함수 AddNum을 호출하려고 할 때, 호출이 불가능함을 확인할 수 있다


 


 

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 SoSimple

{

private:

        int num;

 

public:

        SoSimple(int n) : num(n)

        { }

        SoSimple AddNum(int n)

        {

               num += n;

               return *this;

        }

        void ShowData() const

        {

               cout << "num: " << num << endl;

        }

};

 

 

int main(void)

{

        const SoSimple sim(20);        // const 객체 생성

        // sim.AddNum(30);             // 멤버함수 AddNum const함수가 아니라서 호출 불가

        sim.ShowData();                // 멤버함수 ShowData const함수이니 호출 가능

        return 0;

}

 

Output:

1

num: 20

 

 

const와 함수 오버로딩

함수의 오버로딩이 성립하려면 매개변수 수나 자료형이 달라야 한다.

그 외에 const 선언 유무도 함수 오버로딩의 조건에 해당된다.

 

void SimpleFunc() { . . . . }

void SimpleFunc() 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

32

33

34

35

36

37

38

39

40

41

42

#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       // Const Overloading

        {

               cout << "const SimpleFunc: " << num << endl;

        }

};

 

void YourFunc(const SoSimple &obj)    // 전달되는 인자가 const 참조자

{

        obj.SimpleFunc();              // 참조자를 이용한 함수 호출결과로 const멤버함수 호출

}

 

int main(void)

{

        SoSimple obj1(2);

        const SoSimple obj2(7);        // const 객체 생성

 

        obj1.SimpleFunc();    

        obj2.SimpleFunc();     // const 객체를 대상으로 함수를 호출했기에 const 멤버 함수가 호출됨

 

        YourFunc(obj1); // const 멤버 함수 호출

        YourFunc(obj2); // const 멤버 함수 호출

        return 0;

}


Output:

1

2

3

4

SimpleFunc: 2

const SimpleFunc: 7

const SimpleFunc: 2

const SimpleFunc: 7

 

 

friend 선언

: private 멤버의 접근을 허용하는 선언

 

  • A 클래스가 B 클래스를 대상으로 friend 선언을 하면, B 클래스는 A 클래스의 private 멤버에 직접 접근이 가능하다.
  • , A 클래스도 B 클래스의 private 멤버에 직접 접근 가능하려면, B 클래스가 A클래스를 대상으로 friend 선언을 해줘야 한다.

1

2

3

4

5

6

7

8

9

10

11

12

13

class Boy

{

private:

    int height;

    friend class Girl;    // Girl 클래스를 friend 

public:

    Boy(int len) : height(len)

    { }

    void ShowYourFriendInfo(Girl &frn);

};

Colored by Color Scripter

cs

 

Colored by Color Scripter

cs



5행의 friend선언으로 Girl 클래스 내에서는 Boy 클래스의 모든 private 멤버에 직접 접근이 가능해진다.

(물론 Girl 클래스에서 따로 friend 선언을 하지 않으면, Boy 클래스에선 Girl 클래스의 private 멤버 접근이 불가)

 

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

#include <iostream>

#include <cstring>

using namespace std;

 

class Girl;    // Girl이 클래스의 이름임을 알리는 선언

 

class Boy

{

private:

        int height;

        friend class Girl;     // Girl 클래스에 대한 friend 선언 (private 영역에서도 friend선언 가능)

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;

}

 

Output :

Her phone number: 010-1234-5678

His height: 170

 

 

위에서 5행의 클래스 선언이 없이도 컴파일이 되는데 11행의 선언이 Girl이 클래스의 이름임을 알리는 역할도 있기 때문이다.

 

       

friend class Girl;    // Girl은 클래스의 이름이며, Girl 클래스를 friend 선언한다!

 

 

 

friend 선언은 신중해야한다

: friend 선언은 객체지향의 '정보 은닉'에 반하는 선언이므로  신중히 선언해야 한다.

 

friend 선언은 연산자 오버로딩에서 유용하게 쓰이게 된다.

 

 

함수의 friend 선언

전역변수를 대상으로도, 클래스의 멤버 함수를 대상으로도 friend 선언이 가능하다.

friend로 선언된 함수는 자신이 선언된 클래스의 private 영역에 접근 가능하다.


#include <iostream>
using namespace std;
 
class Point;   // 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&);       // PointOp클래스의 멤버함수 PointAdd에 대한 friend 선언
        friend Point PointOP::PointSub(const Point&, const Point&);       // PointOp클래스의 멤버함수 PointSub에 대한 friend 선언
        friend void ShowPointPos(const Point&);       // 58행에 정의된 함수 ShowPointPos에 대한 friend선언
};
 
Point PointOP::PointAdd(const Point& pnt1, const Point& pnt2)
{
        opcnt++;
        return Point(pnt1.x + pnt2.x, pnt1.y + pnt2.y);      // 30행 friend선언으로 인해 private 멤버 접근 가능
}
 
Point PointOP::PointSub(const Point& pnt1, const Point& pnt2)
{
        opcnt++;
        return Point(pnt1.x - pnt2.x, pnt1.y - pnt2.y);      // 31행 friend선언으로 인해 private 멤버 접근 가능
}
 
int main(void)
{
        Point pos1(1, 2);
        Point pos2(2, 4);
        PointOP op;
 
        ShowPointPos(op.PointAdd(pos1, pos2));
        ShowPointPos(op.PointSub(pos2, pos1));
        return 0;
}
 
void ShowPointPos(const Point& pos)
{
        cout << "x: " << pos.x << ", ";       // 32행 friend선언으로 인해 private 멤버 접근 가능
        cout << "y: " << pos.y << endl;       // 32행 friend선언으로 인해 private 멤버 접근 가능
}


Output:

1
2
3
x: 3, y: 6
x: 1, y: 2
Operation times: 2




1

friend void ShowPointPos(const Point&);

cs

이는 friend 선언이외에 함수 원형 선언이 포함되어 있다.

 

void ShowPointPos(const Point&);



C++에서의 static


C에서와 같이 전역변수와 지역변수에 내릴수 있는 static 선언의 의미는 C++에서도 통용된다.


전역변수에 선언된 static

: 선언된 파일내에서만 참조를 허용 

분할 컴파일시, 파일간에 접근이 불가능해진다. (개별화 됨)


지역변수에 선언된 static

: 전역변수의 성격을 지니게 됨, 지역변수와 달리 함수를 빠져나가도 소멸되지 않음

처음 1회만 초기화되고, 프로그램 종료시까지 메모리 상주(전역변수의 특성)

선언된 함수내에서만 접근 가능(지역변수의 특성)



static선언된 변수는 전역변수와 같이 따로 초기화하지 않을 시, 0으로 자동 초기화된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

void Counter()
{		
	static int cnt;	
	cout << "Current cnt: " << cnt << endl;
	cnt++;
}

int main()
{
	for (int i = 0; i<10; i++)
	{
		Counter();
	}	
	return 0;
}


Output:

1
2
3
4
5
6
7
8
9
10
Current cnt: 0
Current cnt: 1
Current cnt: 2
Current cnt: 3
Current cnt: 4
Current cnt: 5
Current cnt: 6
Current cnt: 7
Current cnt: 8
Current cnt: 9

 


 static 멤버 변수 (클래스 변수)

: static 변수는 객체 별로 존재하는 변수가 아니라, 프로그램 전체 영역에서 하나만 존재하는 변수로 

프로그램 실행과 동시에 초기화되어 메모리 공간에 할당된다.



아래 예제의 14행과 같이, 함수밖에서 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 << "번째 SoSimple 객체" << endl;
	SoSimple sim1;
	SoSimple sim2;

	cout << SoSimple::simObjCnt << "번째 SoSimple 객체" << endl;
	cout << sim1.simObjCnt << "번째 SoSimple 객체" << endl;
	cout << sim2.simObjCnt << "번째 SoSimple 객체" << endl;
	return 0;
}

Output:
1
2
3
4
0번째 SoSimple 객체
2번째 SoSimple 객체
2번째 SoSimple 객체
2번째 SoSimple 객체

 

위 예제에서 23,24행과 같이 객체의 이름을 이용해 접근하는 것은 좋지 않다.

멤버 변수에 접근하는 건지 static 변수에 접근하는 건지 혼동될 수 있어 잘 쓰지 않는다.


따라서 22행과 같이 class를 지정해 접근하는 것이 일반적이고 좋은 방법이다. 


또한 6행과 같이 public 선언되어있지않다면 class 외부에서는 static 멤버 변수에 접근이 불가능하다.



static 멤버 함수


- 특징 (static 멤버 변수의 특징과 일치)

  • 선언된 클래스의 모든 객체가 공유한다.
  • public 선언되면, 클래스를 지정해 호출 가능
  • 객체의 멤버로 존재하는 것이 아니다!

위의 그림을 다시 상기해보면 static 변수와 static 함수는 객체의 생성과 아무런 연관이 없으며, 

객체 내부에 존재하는 변수가 아님을 알 수 있다.


객체와 동시에 생성되는 것이 아닌 전역 변수와 같이 앞서 메모리 공간에 할당된 상태이다.


아래 예제에서 14행의 주석을 해제하면 

"비정적 멤버 'SoSimple::num1'에 대한 참조가 잘못되었습니다. " 라는 에러 메시지가 뜨게 된다.


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 SoSimple
{
private:
    int num1;
    static int num2;
public:
    SoSimple(int n) : num1(n)
    { }
    static void Adder(int n)
    {
        // num1+=n;
        num2 += n;
    }
    void ShowData() const
    {
        cout << "num: " << num1 << num2 << endl;
    }
};
int SoSimple::num2 = 0;
 
int main(void)
{    
    return 0;
}
cs

static 함수는 객체내에 존재하는 함수가 아니기 때문에, 멤버 변수나 멤버함수에 접근이 불가능하다.

당연히 static 변수에만 접근 가능하고, static 함수만 호출 가능하다.



const static 멤버


상수 정보를 담기위한 클래스 CountryArea


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<<"러시아 면적: "<<CountryArea::RUSSIA<<"㎢"<<endl;
	cout<<"캐나다 면적: "<<CountryArea::CANADA<<"㎢"<<endl;
	cout<<"중국 면적: "<<CountryArea::CHINA<<"㎢"<<endl;
	cout<<"한국 면적: "<<CountryArea::SOUTH_KOREA<<"㎢"<<endl;
	return 0;
}


Output:

1
2
3
4
러시아 면적: 1707540㎢
캐나다 면적: 998467㎢
중국 면적: 957290㎢
한국 면적: 9922㎢



mutable 선언

: mutable로 선언된 멤버 변수는 const 함수내에서도 값의 변경이 가능


#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;
}


Output:

1
2
1, 2
1, 1

mutable의 선언은 신중해야 한다!
: 이와 같은 선언은 const 선언에 반하기 때문에, 타당한 이유를 가지고 가급적 제한적으로 선언하는 것이 좋다.


728x90