[04-1] 정보은닉

김민성·2022년 7월 20일
post-thumbnail

정보은닉의 이해

윈도우의 그림판과 같은 성격의 프로그램을 C++을 이용해 구현한다고 가정해보자. 그럼 다양한 종류의 클래스를 정의해야할 것이다. 특히 다음과 같이 점의 위치좌표를 표현하는 목적의 클래스는 기본적으로 필요하다.

class Point
{
public:
	int x;	//0이상 100이하
    int y;	//0이상 100이하
};

우리는 위 클래스를 가지고 정보은닉에 대해 이야기를 해보고자 한다. 다음 예제는 Point 클래스의 멤버변수가 public으로 선언되었을 때 발생할 수 있는 문제점을 보이고 있다.

RectangleFault.cpp

#include <iostream>
using namespace std;

class Point {
    public:
        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;
        }
};

int main(void) {
    Point pos1 = {-2, 4};
    Point pos2 = {5, 9};
    Rectangle rec = {pos2, pos1};
    rec.ShowRecInfo();
    return 0; 
}
좌 상단: [5, 9]
우 하단: [-2, 4]

예제에서 보이듯이 멤버변수를 public으로 정의함으로써 외부접근을 허용하면, 잘못된 값이 저장되는 문제가 발생할 수 있다. 따라서 멤버변수의 외부접근을 막게 되는데, 이를 가리켜 정보은닉이라 한다.

Rectangle 객체 안에는 두 개의 Point 객체가 포함되어 있다. 다음 문장의 실행 결과를 보자.

Rectangle rec = {pos2, pos1};

위 예제의 흐름을 파악한 결과, 어떠한 문제가 있느냐.

  • 점의 좌표는 0이상 100이하가 되어야 하는데, 그렇지 못한 Point 객체가 있다.
  • 직사각형을 의마하는 Rectangle 객체의 좌 상단 좌표 값이 우 하단 좌표 값보다 크다.

따라서 제한된 방법으로의 접근만 허용을 해서 잘못된 값이 저장되지 않도록 도와야 하고, 또 실수를 했을 때, 실수가 쉽게 발견되도록 해야 한다.
어떻게 해야 이러한 것이 가능하겠는지 다음의 변경된 Point 클래스를 보자.

Point.h

#ifndef __POINT_H__
#define __POINT_H__

class Point {
    private:
        int x; //x좌표의 범위는 0이상 100이하
        int y; //y좌표의 범위는 0이상 100이하
    public:
        bool InitMembers(int xpos, int ypos);
        int GetX() const; //x값 반환
        int GetY() const; //y값 반환
        bool SetX(int xpos);
        bool SetY(int ypos);
};

#endif

멤버변수 x와 y를 private으로 선언해 임의로 값이 저장되는 것을 막아놓았다. 따라서 이 함수 내에서 멤버변수에 저장되는 값을 제한할 수 있게 되었다. 그럼 이 함수들이 어떻게 정의되어 있는지 보자.

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

위 코드를 보면 벖어난 범위의 값 저장을 원천적으로 막고 있다는 것을 알 수 있다.

함수만 한번 잘 정의되면 잘못된 접근은 원천적으로 차단된다! 하지맊 정보은닉을 하지 않는다면, 접근할 때마다 주의해야 한다!

이어서 정보가 은닉된 Rectangle 클래스를 보자.

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 클래스도 멤버변수를 private으로 선언하고, 멤버의 초기화를 위한 별도의 함수를 추가 했다. 이 함수 내에는 좌 상단과 우 하단의 좌표가 뒤바뀌는 것을 검사하는 내용이 담겨있는데, 어떻게 검사를 하는지 확인해보자.

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

InitMembers 함수를 보면 좌 상단과 우 하단이 바뀌는 것을 근본적으로 차단되는 모습을 볼 수 있다.

RectangleFaultFind.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]

const 함수

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

const 함수 내에서는 동일 클래스에 선언된 멤버변수의 값을 변경하지 못한다.

매개변수도 아니고, 지역변수도 아닌, 멤버변수에 저장된 값을 변경하지 않겠다는 선언이다. 따라서 const 선언이 추가된 멤버함수 내에서 멤버변수의 값을 변경하는 코드가 삽입되면, 컴파일 에러가 발생한다. 이렇게 함수를 const로 선언하면, 실수로 자신의 의도와 다르게 멤버변수의 값을 변경했을 때, 컴파일 에러를 통해서 이를 확인할 수 있다.

class SimpleClass
{
private:
	int num;
public:
	void InitNum(int n)
    {
    	num=n;
    }
    int GetNum()	//const 선언이 추가되어야 아래의 컴파일 에러 소멸
    {
    	return num;
    }
    void ShowNum() const
    {
    	cout<<GetNum()<<endl;	//컴파일 에러 발생
    }
};

위의 클래스 정의에서 ShowNum 함수는 const 함수로 선언되었다. 그런데 실제로 함수 내에서는 멤버변수 num의 값을 변경하지 않는다.

const 함수는 const가 아닌 함수를 호출하지 못한다! 간접적인 멤버의 변경 가능성까지 완전히 차단한다.

class EasyClass
{
private:
	int num;
public:
	void InitNum(int n)
    {
    	num=n;
    }
    int GetNum()	//const 선언이 추가되어야 아래의 컴파일 에러 소멸
    {
    	return num;
    }
};

class LiveClaa
{
private:
	int num;
public:
	void InitNum(const EasyClaa &easy)
    {
    	num=easy.GetNum(); 	//컴파일 에러 발생
    }
};

C++에서는 const 참조저를 대상으로 값의 변경 능력을 가진 함수의 호출을 허용하지 않는다(실제 값의 변경여부에 상관없이). 따라서 const 참조자를 이용해서는 const 함수만 호출이 가능하다.

profile
다양한 활동을 통해 인사이트를 얻는 것을 즐깁니다. 저 또한 인사이트를 주는 사람이 되고자 합니다.

0개의 댓글