관리 메뉴

Kim's Programming

C++ - 캡슐화(1/3) 본문

Programming/Cplusplus

C++ - 캡슐화(1/3)

Programmer. 2015. 9. 19. 14:55

정보 은폐



정보은폐란 사용자에게 클래스의 사용법만 알려주고 그 내부 구조는 알려주지 않는 것입니다. 실제 클래스 JpegImage클래스는 Jpeg 이미지 파일을 관리하는 클래스이며 이미즈를 관리하고 출력할 수 있는 기능들이 캡슐화되어 있습니다. 우선 JpegImage 클래스를 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class JpegImage
{
private:
    BYTE *RawData;
    JPEGHEADER Header;
    void Decomp();
    void Encomp();
 
public:
    Jpeg();
    ~Jpeg();
    BOOL Load(char *FileName);
    BOOL Save(char *FileName);
    void Draw(int x, int y);
};
cs

손실 압출을 사용하는 Jpeg 파일의 내부는 복잡해서 직접 Jpeg 파일로부터 이미지를 읽으려면 압축 해제 방법, 헤더의 구조, 버전별 차이 등 많은 것을 알아야하고 압축을 풀기 위해 비트를 직접 다루는 어려운 작업을 해야합니다. 하지만 사용자들이 원하는 것은 Jpeg의 구조나 압축 원리가 아니라 Jpeg 출력입니다. 이 클래스를 쓰면 몇 줄의 코드로 간단하게 이미지를 읽을 수 있습니다.

1
2
3
JpegImage Jpeg;
Jpeg.Load("image.jpg");
Jpeg.Draw(10,10);
cs

JpegImage 객체를 하나 만들고 Load 함수로 원하는 파일을 읽은다음 Draw로 화면에 출력하기만 하면 됩니다. 알고리즘, 비트 조립 출력방법등은 사용자는 역시나 관심이 없습니다. 그렇기 떄문에 RawData, Header 같은 멤버 변수를 숨겨서 공개하지 않고 압축해제하는 함수인 Decomp()도 마찬가지로 은폐를 합니다. 사용자가 이 클래스를 쓰기 위해서 알아야 하는 것은 Load, Save, Draw 뿐입니다. 이 함수들이 바로 인터페이시스 이며 최소한의 인터페이스만 공개하는 것이 추상화의 정의입니다. 만약 JpegImage 클래스에 확대 등 여러 기능을 더 제공하더라도 공개된 멤버 함수 호출만 할 줄 알면 됩니다. C++은 클래스의 정보 은폐 기능을 지원하기 위해 private, public, protected등의 액세스 지정자를 제공하며 액세스 지정자로 숨길 멤버와 공개할 멤버의 블록을 구성하도록 합니다. 공개된 멤버는 외부에서 자유롭게 읽을 수 있지만 숨겨진 멤버를 참조하려고 하면 컴파일과정에서 에러처리됩니다. 일반적으로 사용자들은 기능이 복잡한 객체의 내부까지 속속들이 알기 어려우며 그러다보면 부주의한 사용으로 인해 프로그램이 오동작하는 일이 빈번합니다. 소프트웨어는 부주의한 사용으로부터 스스로 방어해야합니다.


비공개 멤버에 대해 사용자가 몰라야 하는 또 다른 이유는 클래스의 안정적인 기능 개선을 위해서입니다. 비공개 영역은 사용자가 몰라도 됨과 동시에 알고 싶어도 알 수 없는 영역입니다. 그래서 이 부분은 기존 사용자의 허가 없이 마음대로 뜯어 고치거나 기능을 개선할 수 있으며 이렇게 수정하더라도 공개 영역만 알고 있는 사용자들은 이 객체를 원래 쓰던 방법 그대로 사용할 수 있습니다. 예를 들어 JpegImage 클래스의 제작자가 더 좋은 압축 하제 알고리즘을 발견했다면 DeComp 함수를 즉시 수정할 수 있으며 이때 이미 이 클래스를 사용하는 코드는 DeComp를 직접 사용하지 않았으므로 별다른 영향을 받지 않습니다. 그러면서도 개선된 알고리즘의 혜택을 받을 수는 있습니다.



프렌드 함수


정보를 은폐하면 객체의 신뢰성이 높아지고 기능 개선도 용이한 것은 분명합니다. 그러나 솔직히 불편한 면이 있습니다. C++의 액세스 지정자는 너무 엄격해서 일단 숨기면 정상적인 문법으로는 외부에서 이 멤버를 참조할 수 없습니다. 물론 캐스트 연산자와 포인터를 사용하는 비정상적인 문법을 동원하면 가능할 수는 있지만 이식성, 확장성은 포기해야됩니다. 어떤 경우에는 이런 정보 은폐기능이 방해가 될 수도 있기 때문에 예외적으로 지정한 대상에 대해서는 모든 멤버를 공개할 수 있는데 이를 프렌드 지정이라고 합니다.


프렌드는 전역 함수, 클래스, 멤버 함수의 세가지 수준에서 지정할 수 있습니다. 상대적으로 가장 간단한 프렌드 함수부터 알아 보겠습니다. 프렌드로 지정하고 싶은 함수의 원형을 클래스 선언문에 적되 원형앞에  friend라는 키워드를 붙입니다. friend 선언의 위치는 아무래도 상관이 없고 어느 영역에 있더라도 차이가 없지만 클래스 선언부의 선두에 두어 눈에 잘 띄도록 하는 것이 좋습니다. 다음은 function함수를 Myclass의 프렌드로 지정한 것입니다.

1
2
3
4
class Myclass
{
    friend void function();
};
cs

function 함수는 클래스 선언부에 원형이 포함되어 있지만 Myclass 클래스의 멤버는 아니고 본체는 외부에 따로 존재하므로 단순한 전역함수입니다. 하지만 Myclass 클래스 선언부에서 function함수를 프렌드로 지정했으므로 마치 클래스의 소속의 멤버 함수인 것처럼 이 클래스의 모든 멤버를 자유롭게 액세스 할 수 있는 특권이 부여됩니다. private 영역에 있건 public 영역에 있건 어 떤 멤버 변수든지 읽고 쓸 수 있으며 모든 멤버 함수를 자유롭게 호출 할 수 있습니다. 다음은 friend의 예시입니다.

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 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)
{
    cout << "오늘은 " << d.year << "년" << d.month << "월" << d.day << "일" << "이며" << endl
        << "지금 시간은 " << t.hour << ":" << t.min << ":" << t.sec << "입니다." << endl;
}
 
void main()
{
    Date D(2015919);
    Time T(112915);
    OutToday(D, T);
}
cs

Date는 날짜를 표현하는 클래스이며 Time은 시간을 표현하는 클래스입니다. 정보를 기억하는 주요 변수들은 모두 private 영역에 선언되어 있어 외부에서 함부로 액세스하지 못하도록 하였습니다. OutToday 함수는 이 두 클래스의 객체를 인수로 전달받아 날짜와 시간을 동시에 출력합니다. 그러기 위해서 OutToday는 양쪽 클래스의 모든 멤버를 읽을 수 있어야 하는데 Date나 Time의 멤버 함수로 포함되면 한쪽밖에 읽을 수 없습니다. 한 함수가 동시에 두 클래스의 멤버 함수가 될 수는 없기 떄문입니다. 이럴 때 OutToday를 멤버 함수가 아닌 전역함수로 정의하고 양쪽 클래스에서 이 함수를 프랜드로 지정하면 됩니다. 이렇게 되면 OutToday함수는 클래스 내부의 숨겨진 멤버에 쉽게 접근할 수 있습니다. 만약 프렌드 선언을 하지 않았다면 숨겨진 멤버를 엑세스 할 수 없다는 에러를 잔뜩 줄 것입니다.


OutToday함수는 2 타입의 인수를 동시에 취하는데 이 함수의 원형을 사용하기 전에 두 명칭이 클래스라는 것을 선언해야 합니다. 양쪽 클래스 선언문에 프렌드 지정이 동시에 들어가야 하므로 먼저 선언하는 쪽을 위해 나중에 선언되는 클래스에 대한 전방 선언이 필요합니다. Time클래스 선언 이전에 Date가 클래스라는 것을 먼저 알려야 컴파일러가 OutToday의 원형을 해석할 수 있으며 그래서 class Date가 선두에 선언되었습니다. 위의 경우는 프렌드 뿐만아니라 멤버를 public으로 공개 시켜줘도 되는데 이는 정보 은폐의 원칙에 어긋나게 됩니다. 또한 프렌드를 지정하는 대신 숨겨진 멤버를 읽어주는 함수들을 공개영역에 작성해서 읽을 수도 있습니다. 하지만 이런방식은 귀찮고 번거로워 프렌드를 이용하여 구현한것입니다.


프렌드 클래스


두 클래스가 아주 밀접한 관련이 있고 서로 숨겨진 멤버를 자유롭게 읽어야 하는 상황이라면 클래스를 통째로 프렌드로 지정할 수 있습니다. 클래스 선언문 내에 프렌드로 지정하고 싶은 클래스의 이름을 밝히면 됩니다. 다음은 Myclass 클래스2를 Myclass의 프렌드로 지정하는 것입니다.

1
2
3
4
class Myclass
{
    friend class Myclass2;
};
cs

Myclass2가 Myclass의 프렌드로 지정되었으므로 Myclass2의 모든 멤버 함수들은 Myclass의 모든 멤버를 마음대로 엑세스할 수 있습니다. 두 클래스가 협조적으로 동작해야 한다거나 상호 종속적인 관계에 있을 때 프렌드로 지정하면 편리합니다. 위의 예시를 프렌드 클래스를 이용하여 구현해보겠습니다.

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
#include<iostream>
using namespace std;
 
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)
    {
        cout << "오늘은 " << year << "년" << month << "월" << day << "일" << "이며" << endl
            << "지금 시간은 " << t.hour << ":" << t.min << ":" << t.sec << "입니다." << endl;
    }
};
 
void main()
{
    Date D(2015919);
    Time T(112915);
    D.OutToday(T);
}
cs


프렌드 멤버 함수


프렌드 클래스 지정은 특정 클래스의 모든 멤버 함수들이 자신의 숨겨진 멤버를 마음대로 읽도록 허락 하는 것입니다. 멤버 함수의 수가 많을 경우 모든 멤버 함수들이 대상 클래스의 멤버를 액세스할 필요가 없음으에도 불구하고 허용 범위가 너무 넓어져 위험해집니다. 프렌드 멤버함수는 특정 클래스의 특정 함수만 프렌드로 지정하는 것이며 꼭 필요한 함수에 대해서만 숨겨진 멤버를 액세스하도록 범위를 좁게 설정할 수 있는 장점이 있습니다. 개념은 프렌드 함수와 동일하되 다른 클래스에 속한 멤버 함수라는 것만 다릅니다. 클래스 선언부에 프렌드로 지정하고자 하는 멤버 함수의 원형을 friend 키워드와 함께 적어주면 됩니다. 다음 예는 Myclass2::func 멤버 함수를 Some 클래스의 프렌드로 지정합니다.

1
2
3
4
class Myclass
{
    friend void Myclass2::function(Myclass &M)
};
cs

이렇게 선언하게 되면 Myclass2 클래스의 function 멤버 함수는 Some 클래스의 모든 멤버를 엑세스 할 수 있습니다. 그러나 Myclass2 클래스의 다른 멤버 함수에게는 이런 특권이 부여되지 않습니다. 오로지 Myclass2::function에 대해서만 프렌드 지정을 한 것입니다. 다음 소스는 Date::OutToday 멤버만 프렌드로 지정합니다.

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
#include<iostream>
using namespace std;
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)
{
    cout << "오늘은 " << year << "년" << month << "월" << day << "일" << "이며" << endl
        << "지금 시간은 " << t.hour << ":" << t.min << ":" << t.sec << "입니다." << endl;
}
 
void main()
{
    Date D(2015919);
    Time T(112915);
    D.OutToday(T);
}
cs

OutToday 멤버 함수가 Time의 프렌드로 지정되어 이쓰므로 Time의 멤버들을 자유롭게 액세스 할 수 있습니다. 오로지 이 멤버 함수만 프렌드로 지정되었으므로 다른 멤버 함수들은 여전히 Time 클래스를 액세스할 수 없습니다. 멤버 함수를 프렌드로 지정할 때는 선언순서에 약간 신경을 써야합니다. 프렌드 멤버 함수는 프렌드로 지정되는 클래스 소속이며 통상 대상 클래스를 인수로 전달받기 때문에 프랜드 지정을 포함하는 클래스를 먼저 선언하고 프렌드 멤버 함수를 포함한 클래스를 전방 선언해야합니다.


Time에서 Date::OutToday를 프렌드로 지정하기 위해서는 이 함수의 원형을 먼저 알려야 하므로 Date 클래스가 앞쪽에 선언되어야 합니다. 또한 OutToday에서 Time형 객체를 인수로 전달받으므로 Date 클래스 선언문 이전에 Time이 클래스라는 전방 선언이 필요합니다. 순서가 바뀌면 안됩니다. 그리고 OutToday의 본체에서 Time 객체의 멤버를 참조하므로 이 함수의 본체는 클래스 선언부에 둘 수 없으며 Time 클래스 정의후에 따로 본체를 정의해야 합니다. 만약 OutToday를 인라인으로 만들고 싶다면 본체 정의부에 inline 키워드를 쓰면 됩니다.


두 클래스가 서로를 참조하고 있는 상황이라서 선언 순서가 조금 난잡스럽습니다. 간단히 설명하면 서로 알 수 있도록 소개해주는 겁니다.


프렌드의 특성


프렌드 지정은 몇 가지 특성을 가지고 있는데 가급적이면 명시적으로 선언되지 않은 대상에 대해서 특권을 주지 않는 성질이 있습니다. 코드 작성의 편의를 위해 예외적으로 프렌드라는 것을 도입했지만 그 부작용을 최소화하기위해 마련된 규칙들이고 상식적인 것들입니다.


    1. 프렌드 지정은 단방향이며 명시적으로 지정한 대상만 프렌드가 됩니다. A가 B를 프렌드로 선언했다고 하더라도 B가 A를 프렌드로 선언하지 않으면 A는 B의 프렌드가 아닙니다. 그래서 B는 A의 모든 멤버를 읽을 수 있지만 A는 B를 읽을 수는 없습니다. 만야 A B가 서로 프렌드가 되려면 양쪽 모두 상대방을 프렌드로 지정해야하며 이런 관계를 상호 프렌드라고 합니다.

    2. 프렌드 지정은 전이 되지 않으며 친구의 친구 관계는 인정하지 않습니다. A,B,C 세 개의 클래스가있을때 A는 B를 프렌드로 선언했고 B는 C를 프렌드 선언했을 때 B는 A에 엑세스 할 수 있고 C도 B에 엑세스를 할 수 있지만 C는 A의 숨겨진 멤버에 엑세스 할 수 없습니다. 프렌드지정을 해야 프렌드가 될 수 있습니다. 프렌드 지정은 허가하는 쪽에서 명시적으로 해줘야 합니다.

    3. 복수의 대상에 대해 동시에 프렌드 지정을 할 수 있지만 한 번에 하나씩만 가능합니다. A가 B,C를 동시에 프렌드로 지정하고 싶을 떄는 다음처럼 해야합니다.

      1
      2
      3
      4
      5
      class A
      {
          friend class B;
          friend class C;
      };
      cs

      firend class B,C 이런 방식만 되지 않는다는 겁니다.

    4. 프렌드 관계는 상속되지 않습니다. A가 B를 프렌드로 지정하면 B는 A를 엑세스 할 수 있습니다. 그러나 B로 부터 파생된 D는 A의 프렌드가 아니기 때문에 A를 마음대로 엑세스 할 수 없습니다. 친구의 자식도 역시 친구가 아닙니다. 그러나 D가 상속받은 B의 멤버는 B클래스의 소속이라고 볼 수 있으므로 여전히 A를 엑세스 할 수 있습니다.

프랜드는 OOP의 정보 은폐 원칙에 대한 예외입니다. 숨겨놓은 정보를 읽기 위해서 일일이 엑세스 함수를 경유하는 것은 불편하고 때로는 외부 함수가 내부 멤버를 엑세스 해야 하는 불가피한 경우가 있어서 프렌드가 반드시 필요하지만 자주 사용하는 것은 좋지 않습니다. 프렌드 말곤 방법이 없는 경우에 조심스럽게 사용해야합니다.


정적 멤버

this


멤버 변수는 객체별로 따로 가지며 멤버 함수는 클래스에 속한 모든 객체들이 공유합니다. 멤버 변수는 개별 객체의 상태를 저장하므로 객체별로 유지되는 것이 옳고 멤버 함수가 정의하는 동작은 모든 객체에 공통적으로 적용되므로 공유하는 것이 합당합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
class Myclass
{
private:
    int value;
public:
    Myclass(int input_value) :value(input_value){}
    void Output()
    {
        std::cout << "value = " << value << std::endl;
    }
};
 
void main()
{
    Myclass M(1), M2(3);
    M.Output();
    M2.Output();
}
cs

MyClass는 정수형 멤버 변수 value를 가지며 생성자는 전달받은 인수로 value를 초기화합니다. 유일한 멤버 함수인 Output은 value의 값을 단순하게 출력만 합니다. main함수에선 객체 M 과 M2를 생성했습니다. 하지만 Output은 어떻게 자신이 호출한 객체를 알 까요? 이 함수의 본체 코드에서 value라는 멤버를 이름만으로 참조하고 있는데 이 멤버가 과연 누구의 value인지 어떻게 판단할까요? 함수는 호출원으로부터 정보를 전달받을 때 인수를 사용하는데 Output함수 원형을 보면 어떠한 인수도 받아들이지 않습니다. 입력값인 인수가 없으면 함수의 동작은 항상 같은 수밖에 없음에도 불구하고 Output은 호출한 객체에 따라 다른 동작을 할 수 있습니다.


그 이유를 설명하자면 main에서 Output을 호출할 때 어떤 객체 소속의 멤버 함수를 호출할 것인지 소속 객체를 함수 이름을 앞에 밝혔기 떄문입니다. 코드를 보면 M.Output(); M2.Output();식으로 작성 되어 있어 사람이 눈으로 보기에도 두 호출문은 구분됩니다. 그러나 함수입장에서는 자신을 호출한 문장 앞에 붙어 있는 M,M2따위의 객체 이름을 읽을 수 없으며 인수로 전달되는 값만이 의미가 있습니다.


호출한 객체에 대한 정보가 함수의 인수로 전달되지 않으면 본체는 여전히 호출한 객체를 알 방법이 없습니다. 난수나 시간을 참조하는 특수한 함수를 제외하고 입력값이 일정하면 출력값이 달라질 수 없는 것은 함수의 본질적인 특성입니다. 그래서 멤버 함수가 호출한 객체를 구분하기 위해서는 결국 객체에 대한 정보가 함수의 인수로 전달되어야 하며 C++컴파일러는 호출문의 객체를 함수의 인수로 몰래 전달합니다.

호출한 객체를 멤버 함수로 전달하는 방법은 컴파일러마다 조금씩 다릅니다. 어쨋든 멤버 함수를 호출할 때는 호출한 객체의 정보가 함수에게 암시적으로 전달된다는 것입니다. 그래서 멤버 함수는 호출한 객체별로 다른 동작을 할 수 있고 복수의 객체가 멤버 함수를 공유할 수도 있게 됩니다.


우리는 눈에 명시적으로 보이진 않지만 Output 함수는 자신을 호출한 객체의 번지를 인수로 전달받습니다. 이때 전달받은 숨겨진 인수를 this라고 하는데 호출한 객체의 번지를 가리키는 포인터 상수입니다. 일반적으로 Class형 멤버 함수들은 Class *const this를 받아들이며 this로부터 객체의 고유한 멤버를 엑세스 할 수 있습니다. 위의 소스에서 output 함수는 컴파일러에 의해 다음과 같이 재해석됩니다.

1
2
3
4
void Output(Myclass *const this)
{
    cout<<"value = "<<this -> value);
}
cs

멤버 함수의 본체에서 멤버를 참조하는 모든 문장 앞에는 this->가 암시적으로 적용됩니다. 그래서 멤버 변수 mem에 대한 참조문은 this->mem으로 해석되고 멤버 함수 func() 호출문은 this->func()를 호출하며 실제로 이렇게 써도 똑같이 동작합니다. M.Output()문에 의해 호출된 output함수는 this는 &M의 값을 가지며 따라서 this->value는 M객체의 value멤버가 됩니다. 마찬가지로 M2.Output() 호출시 this는 &M2가 되며 this->value는 M2객체의 value를 의미 합니다.


멤버 함수의 인수가 n개 이면 실제로 이 함수가 호출될 때는 this가 하나 더 전달되므로 항상 n+1개의 인수가 전달되는 셈이됩니다.


 멤버 함수

 실제모양

 function()

 function(this)

 function(int a)

 function(this, int a)

 function(char *p,double d)

 function(this, char *p, double d)

멤버 함수가 객체들에 의해 공유되려면 호출한 객체를 구분해야 하고 그러기 위해서는 호출 객체의 정보를 함수의 인수로 전달해야 하는데 이 처리를 개발자가 직접해야 한다면 무척 귀찮을 것입니다. 만약 이런 식이라면 구조체와 함수를 따로 만들고 함수를 호출할 때마다 구조체를 인수로 넘기는 것과 같으므로 캡슐화래 놓은 의미가 없어지는 셈입니다. 이 작업은 모든 멤버 함수에 공통적으로 필요한 조치이며 획일적이기 때문에 개발자가 별도로 명시하지 않아도 컴파일러가 알아서 자동으로 하도록 되어 있습니다.


예외가 없으므로 개발자가 개입할 필요가 없으며 기계가 이 작업을 대신 할 수 있는 것입니다. 이처럼 멤버 함수 호출시에 this를 암시적으로 전달하는 호출 규약을 thiscall이라고 하는데 모든 멤버 함수에 자동으로 적용됩니다. 단 가변인수를 취하는 멤버 함수는 cdecl 호출 규약을 사용합니다. C++컴파일러가 멤버 함수 를 처리하는 방식을 C언어에서 그대로 따라하면 C로도 객체를 흉내낼 수 있을 것입니다.


this에 대한 모든 관리는 컴파일러가 알아서 처리합니다. 멤버 함수를 호출할 때마다 this를 전달하고 본체의 모든 멤버 참조문 앞에 this ->를 일일이 붙입니다. 그렇다면 개발자가 this의 존재를 굳이 왜 알아야 할가요? 멤버 함수의 본체에서 this 키워드는 지금 이 함수를 실행하고 있는 객체 그 자체를 표현하는 1인칭 대명사입니다. 멤버 함수가 객체를 칭할 필요가 있을 때는 this를 직접 사용해야 합니다. 다음 함수를 Myclass 클래스에 추가해 보겠습니다.

1
2
3
4
5
6
7
Myclass *FindBig(Myclass *Other)
{
    if (Other->value > value)
        return Other;
    else
        return this;
}
cs


이 함수는 인수로 전달된 Other 객체와 자신을 비교하여 더 큰 값을 가진 객체의 포인터를 리턴합니다. 객체의 대소비교 기준은 클래스마다 다르겠지만 Myclass 클래스는 value라는 정수값을 가지고 있으므로 이 값을 비교하면 될 것입니다. value가 비공개 영역에 선언되어 있으므로 객체끼리의 비교는 외부에서 할 수 없으며 클래스의 멤버 함수가 직접해야합니다. Other->value와 value(this(->value의 간략한 표현)를 비교해보고 Other->value의 값이 더 크면 비교결과로 Other를 리턴합니다. 그렇지 않다면 Other보다 자신이 더 크므로 자신을 리턴해야 하는데 이때 this 키워드가 필요합니다. this가 자신을 가리키는 포인터 상수이므로 this를 리턴하면 됩니다. this가 없다면 이 멤버 함수를 호출한 객체를 칭할 방법이 없습니다. 다음 2가지 코드는 두 객체 중 큰 값을 가진 객체를 찾아 이 객체의 value를 출력하는데 두 코드 모두 결과는 같습니다. A가 B를 비교하나 B가 A를 비교하나 마찬가지입니다.

1
2
M.FindBig(&M2)->Output();
M2.FindBig(&M)->Output();
cs

객체는 보통 단순타입보다는 크기 떄문에 함수의 인수로 전달할 때는 포인터나 레퍼런스를 사용하는 것이 유리합니다. FindBig 함수를 다음과 같이 수정해도 동일하게 동작합니다.

1
2
3
4
5
6
7
Myclass &FindBig(Myclass &Other)
{
    if(Other.value > value)
        return Other;
    else
        return *this;
}
cs


자기 자신에 대한 레퍼런스를 리턴할 때는 *this표현식을 사용합니다. 함수를 이렇게 수정한 후 호출부에서 비교 대상을 포인터가 아닌 객체로 전달하면 됩니다. 비교 결과 리턴되는 값도 레퍼런스이므로 ->연산자 대신 . 연산자로 Output을 호출하면 됩니다. 호출부를 M.FindBig(B).Output();로 수정하면 동일하게 동작합니다. 꼭 원한다면 포인터나 레퍼런스가 아닌 객체 자체를 인수로 넘기고 리턴을 받을 때도 객체를 돌려받을 수 있는데 FindBig 함수의 원형을 Myclass FindBig(Myclass Other)로 고치기만 하면 됩니다. 이 방법은 속도에도 불리하고 인수 전달 과정에서 여러 가지 부작용이 발생할 수도 있으므로 권장되지 않습니다.


객체가 자신을 스스로 삭제하고자 할 떄도 this 키워드를 씁니다. 치명적인 에러나 프로그램 종료시 스스로 자살하고자 할 때 delete this; 한줄이면 가볍게 생을 마감할 수 있습니다. 물론 이 객체는 동적으로 할당된 객체여야만 합니다. delete this;를 말로 해석해보면 나를 죽여달라는 말이 되는데 나라는 것을 가르키기 위해 this가 필요합니다. 자신을 스스로 삭제하는 delete this; 문장은 자동화된 객체 관리를 위해 종종 사용됩니다.


객체의 멤버 함수에서 자신을 칭할 필요는 늘상있습니다. memset(this,0,sizeof(*this))는 자신의 모든 멤버를 0으로 리셋하며 function(this)는 function 전역함수로 자신을 전달합니다. 자바나 비주얼 베이직 등의 다른 언어에서 객체가 지신을 칭할때는 self, me등의 키워드를 사용하는데 이 키워드는 C++의 this와 같습니다. 멤버 함수의 지역변수와 멤버 변수와의 이름 충돌이 발생했을 때도 this를 사용합니다. 예를 들어 다음 클래스를 보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
class Myclass
{
private:
    int i;
public:
    void function()
    {
        int i;
        i = 3;
    }
};
cs

멤버 함수 function에 3을 대입하는 i는 멤버 변수i가 아니라 지역변수 i 입니다. 멤버 변수와 같은 이름을 가진 지역변수가 선언되어 있을 때 지역변수 대신 멤버 변수를 액세스하고 싶다면 Myclass::i 또는 this->i로 소속을 명확하게 밝혀야 합니다. 물론 불가피하게 출동이 발생했을 때 이렇게 해결할 수 있다는 것이지 이 경우는 지역변수의 이름을 멤버 변수와 다른 것으로 바꾸는 것이 더 바람직 합니다.

'Programming > Cplusplus' 카테고리의 다른 글

C++ - 캡슐화(3/3)  (0) 2015.09.19
C++ - 캡슐화(2/3)  (0) 2015.09.19
C++ - 클래스 생성자/파괴자(3/3)  (0) 2015.09.03
C++ - 클래스 생성자/파괴자(2/3)  (2) 2015.08.31
C++ - 클래스 생성자/파괴자(1/3)  (1) 2015.08.30