Notice
Recent Posts
Recent Comments
«   2024/12   »
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
Archives
Today
Total
관리 메뉴

SYDev

C++ Chapter 04-1 : 정보은닉(Information Hiding) 본문

Programming Lang/C++

C++ Chapter 04-1 : 정보은닉(Information Hiding)

시데브 2023. 7. 15. 17:32

정보은닉의 이해

 우리가 클래스를 디자인할 때 좋은 클래스를 만들기 위해서 필요한 조건이 있다. 여기에는 '정보은닉'과 '캡슐화'가 있다. 이는 좋은 클래스가 되기 위한 최소한의 조건인데, 이번에는 우선 정보은닉에 대해 알아보자.

 

 

 정보은닉을 이해하기 위해서 클래스 하나를 예시로 들어보자. 그림판에서 도형을 그리는 프로그램을 구현하기 위해서 위치좌표를 나타내는 클래스는 필수적이다. 이 클래스의 이름은 Point이고 x, y 좌표의 범위는 각각 0이상 100이하, 그리고 좌 상단의 좌표는 [0, 0]이고 우 상단의 좌표는 [100, 100]이라고 가정해보자. 이 클래스의 멤버변수가 public으로 선언되었을 때의 문제점을 살펴보자.

 

#include <iostream>
using namespace std;

class Point
{
public: // 멤버변수가 public으로 선언되면 init 함수 없이도 초기화가 가능
    int x; // x좌표 범위는 0이상 100이하
    int y; // y좌표 범위는 0이상 100이하
};

class Rectangle
{
public:  
    Point upLeft;
    Point lowRight;

public:
    void ShowRecInfo()
    {
        cout<<"좌 상단: "<<'['<<upLeft.x<<',';
        cout<<upLeft.y<<']'<<endl;
        cout<<"우 하단: "<<'['<<lowRight.x<<',';
        cout<<lowRight.y<<']'<<endl<<endl;
    }
};

int main(void)
{
    Point pos1 = {-2, 4}; // 좌표의 범위가 0 아래로 내려갔다.
    Point pos2 = {5, 9};  //직사각형을 만드려면 좌상단의 x좌표값이 우상단의 x좌표값보다 작아야 한다.
    Rectangle rec = {pos2, pos1}; // 객체는 멤버로 입력 가능함
    rec.ShowRecInfo();

    return 0;
}
좌 상단: [5,9]
우 하단: [-2,4]

 위 예제의 문제점은 첫 번째로 점의 좌표 범위가 지켜져야 하는데 그렇지 않은 point 객체가 하나 있고, 두 번째로  Rectangle 객체의 좌상단의 x좌표값이 우상단의 x좌표값보다 작아야 하는데 이 또한 지켜지지 않고 있다.

 

 프로그래머의 실수는 종종 발생할 수 있다. 또한, 프로그래머의 실수는 어떻게든 발견되어야 한다. 그러나 위와 같은 종류의 실수들은 문법적으로 오류가 없기 때문에 컴파일러에서 걸러지지 않아 쉽게 발견되지 않는다. 그렇다면 우리는 제한된 방법으로의 접근만 허용을 해서 잘못된 값이 저장되지 않도록 해야 하고, 실수가 발생하면 이런 실수가 쉽게 발견되도록 해야 한다. 

 

 이런 문제들이 개선된 다음 예제를 확인해보자.

 파일명 : Point.h

#ifndef __POINT_H_
#define __POINT_H_

class Point
{
private:
    int x;
    int y;

public:
    bool InitMembers(int xpos, int ypos);
    int GetX() const;
    int GetY() const;
    bool SetX(int xpos);
    bool SetY(int ypos);
};

#endif

 가장 먼저 멤버변수 x와 y를 private으로 선언해서 임의로 값이 저장되는 것을 막고(변수 x, y의 정보를 은닉), 따로 값의 저장 및 참조를 위한 함수를 추가로 선언하였다. 

 

 파일명 : Point.cpp

#include <iostream>
#include "Point.h"
using namespace std;

bool Point::InitMembers(int xpos, int ypos)
{
    if(xpos<0 || ypos<0)
    {
        cout<<"벗어난 범위의 값 전달"<<endl;
        return false;
    }

    x=xpos;
    y=ypos;

    return true;
}

int Point::GetX() const //const 함수
{
    return x;
}

int Point::GetY() const //const 함수는 이후에 설명
{
    return y;
}

bool Point::SetX(int xpos)
{
    if(0>xpos || xpos>100)
    {
        cout<<"벗어난 범위의 값 전달"<<endl;
        return false;
    }
    x=xpos;
    
    return true;
}

bool Point::SetY(int ypos)
{
    if(0>ypos || ypos>100)
    {
        cout<<"벗어난 범위의 값 전달"<<endl;
        return false;
    }
    y=ypos;
    
    return true;
}

 해당 파일에서는 멤버변수 x, y의 값을 저장하는 함수 InitMebers, SetX, SetY의 내부 구조를 살펴보면, x, y 의 값의 범위가 0이상 100이하가 되지 않으면 오류메시지를 출력하며 값의 저장을 허용하지 않는다. 

 

 그렇다면 우리는 정보은닉을 다음과 같이 정의할 수 있다. "멤버변수를 private으로 선언하고, 그 멤버변수에 접근할 수 있는 함수를 별도로 선언해 해당 멤버변수의 접근에 오류가 없도록 하는 것이 '정보은닉'이다."  

 

 또한, 우리는 위 파일에서 Get... Set...의 이름을 가진 함수들을 확인할 수 있는데, 이들을 가리켜 '액세스 함수(access function)'이라고 한다. 이들은 멤버변수를 private으로 선언하면서 클래스 외부에서 멤버변수의 접근을 목적으로 하는 함수들이다.

 

+ 이들 함수는 정의되었으나 호출되지 않는 경우도 많다. -> 클래스를 정의할 때 호출될 함수 위주로 멤버함수를 구성하는 것이 맞긴 하나, 지금 당장은 필요 없어도 나중에 필요할 가능성이 있는 함수들을 포함시키는 경우도 많기에 혼동하지 말자.

 

 이어서, 다음 파일들을 살펴보자.

 

 파일명 : Rectangle.h

#ifndef __RECTANGLE_H_
#define __RECTANGLE_H_

#include "Point.h"

class Rectangle
{
private:
    Point upLeft;
    Point lowRight;

public:
    bool InitMembers(const Point &ul, const Point &lr);
    void ShowRecInfo() const;
};

#endif

 

 파일명 : Rectangle.cpp

#include <iostream>
#include "Rectangle.h"
using namespace std;

bool Rectangle::InitMembers(const Point &ul, const Point &lr)
{
    if(ul.GetX()>lr.GetX() || ul.GetY()>lr.GetY())
    {
        cout<<"잘못된 위치정보 전달"<<endl;
        return false;
    }
    upLeft = ul;
    lowRight = lr;
    
    return true;
}

void Rectangle::ShowRecInfo() const
{
    cout << "좌 상단: " << '[' << upLeft.GetX() << ',';
    cout << upLeft.GetY() << ']' << endl;
    cout << "우 하단: " << '[' << lowRight.GetX() << ',';
    cout << lowRight.GetY() << ']' << endl<< endl;
}

 

 파일명 : RectangleFalutFind.cpp

#include <iostream>
#include "Point.h"
#include "Rectangle.h"
using namespace std;

int main(void)
{
    Point pos1;
    if(!pos1.InitMembers(-2,4))
        cout<<"초기화 실패"<<endl;
    if(!pos1.InitMembers(2,4))
        cout<<"초기화 실패"<<endl;

    Point pos2;
    if(!pos2.InitMembers(5,9))
        cout<<"초기화 실패"<<endl;

    Rectangle rec;
    if(!rec.InitMembers(pos2,pos1))
        cout<<"직사각형 초기화 실패"<<endl;
    if(!rec.InitMembers(pos1,pos2))
        cout<<"직사각형 초기화 실패"<<endl;    

    rec.ShowRecInfo();

    return 0;
}
벗어난 범위의 값 전달
초기화 실패
잘못된 위치정보 전달
직사각형 초기화 실패
좌 상단: [2,4]
우 하단: [5,9]

 앞서 선언한 초기화 관련 함수들은 모두 성공 혹은 실패 여부에 따라서 true, false를 반환하도록 정의했기 때문에, 이를 이용해서 오류를 쉽게 찾아내는 간단한 프로그램을 작성해봤다.  

 

 그렇다면 이번에는 중간중간 사용한 const 함수에 대해서 살펴보자.

 

const 함수

 위 예제를 작성할 때 우리는 다음과 같은 함수들을 선언했다.

int GetX() const;
int GetY() const;
void ShowRecInfo() const;

 다음과 같은 함수들을 const 함수라 하는데 기본적으로 함수의 선언 뒤에 const 선언만 추가되어있는 형태이다. 이런 const 함수해당 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다는 의미를 가진다. 이렇게 const선언이 된 함수 내에서 멤버변수 값을 변경하려는 시도를 하면 컴파일 에러가 발생한다.

 

 이런 const 함수에는 또다른 특징이 있는데 예제를 통해서 살펴보자.

class SimepleClass
{
    private:
        int num;

    public:
        void InitNum(int n)
    {
        num = n;
    }
    
    int GetNum() // const 선언을 추가해야 아래의 compile error가 소멸됨
    {
        return num;
    }

    void ShowNum() const
    {
        cout<<GetNum()<<endl; // compile error
    }
};

 const 함수 내부에서는 const가 선언되지 않은 함수의 호출이 제한된다. 함수 내부에서 멤버변수의 값을 변경하지 않더라도 const가 선언되지 않은 함수는 변경할 수 있는 능력을 지닌 함수이기 때문에 아예 제한된다.

 

 이와 비슷한 특성을 확인하는 예제를 하나 더 살펴보자.

class EasyClass
{
private:
    int num;

public:
    void InitNum(int n)
    {
        num = n;
    }

    int GetNum() // const 선언을 추가해야 아래의 compile error가 소멸됨
    {
        return num;
    }
};

class LiveClass
{
private:
    int num;

public:
    void InitNum(const EasyClass &easy)
    {
        num = easy.GetNum(); //compile error
    }
}

 LiveClass 정의에서 InitNum의 매개변수 easy는 'const 참조자'이기 때문에 이를 대상으로 const 선언이 되지 않은 GetNum() 함수를 호출하면 컴파일 에러가 발생한다. 위에서 다룬 특성과 비슷하게 c++에서는 const 참조자를 대상으로 값의 변경 능력을 가진 함수 호출을 허용하지 않는다.

 


출처 : 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12