본문 바로가기
Object Oriented Programming(C++)/열혈 C++ 프로그래밍

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

by continue96 2021. 8. 9.

1. 정보 은닉

1.1 정보 은닉의 이해

 C++는 구조체 외부에서 구조체의 모든 멤버 변수와 멤버 함수에 쉽게 접근할 수 있다. 하지만 클래스는 객체지향 언어가 지향하는 정보 은닉(Information Hiding)을 고려해야 한다. 정보 은닉이란 클래스 외부에서 멤버 변수에 함부로 접근할 수 없게 하고 대신 클래스 내부에서 간접적으로 접근하는 멤버 함수를 정의하여, 안전하고 올바른 방향으로 멤버 변수에 접근할 수 있도록 유도하는 것을 말한다. 바로 이 정보 은닉을 위해 C++는 접근제어 지시자(Access Control Specifiers) 키워드를 제공하고, 클래스 외부에서 멤버 변수 또는 멤버 함수로 접근할 수 없게 한다. 접근제어 지시자 키워드는 private, protected, 그리고 public이 있는데 키워드별 효과는 다음과 같다.

 

접근제어 지시자 설명
public 클래스 안팎 모든 곳에서 접근할 수 있다.
protected 상속받은 자식 클래스 안에서 접근할 수 있다.
private 클래스 안에서 멤버 함수로 접근할 수 있다.

 

 정보 은닉을 이해하기 위해 예를 하나 들어보자. 좌표 평면 중 1사분면에서 x좌표와 y좌표가 0보다 크고 100보다 작은 두 점을 받아 직사각형을 그리는 프로그램을 생각해보자. 여러분도 알다시피, 1사분면은 점의 좌표가 모두 양의 실수로 이루어진 좌표 평면이다. 따라서 1사분면과 x좌표와 y좌표의 값이 0 초과 100 미만이라는 두 조건을 고려하였을 때, 다음 문장은 문제를 발생시키는 경우라고 할 수 있다.

 

  1. 사용자가 멤버 변수에 음의 실수직접 입력하는 경우
  2. 사용자가 멤버 함수에 음의 실수를 전달하는 경우
  3. 사용자가 전달한 우상단/좌하단의 좌표가 좌하단/우상단의 좌표보다 더 작을/클 경우

 

RectangleFault.cpp
/* 정보가 은닉되지 않은 Point 클래스 */
#include <iostream>
using namespace std;

class Point {
public: /* public으로 선언되었습니다. */
	int x, y;
};

class Rectangle {
public: /* public으로 선언되었습니다. */
	Point upRight;
	Point downLeft;

public:
	void DrawRectangle() {
		cout << "우상단: " << "[ " << upRight.x << ", " << upRight.y << " ]\n";
		cout << "좌하단: " << "[ " << downLeft.x << ", " << downLeft.y << " ]\n";
	}
};

int main(void) {
	Point upRight = { -10, -10 };	/* 사용자가 음의 실수를 좌표로 입력합니다. */
	Point leftDown = { 10, 10 };	/* 사용자가 좌하단의 좌표를 우상단의 좌표보다 더 크게 설정합니다. */
	Rectangle rec = { upRight, leftDown };
	rec.DrawRectangle();
	return 0;
}

 위의 예제는 정보가 은닉되지 않은 예제로, 22행을 살펴보면 Point의 멤버 변수가 public으로 선언되어 우상단 좌표가 (-10, -10)으로 음의 실수가 입력되었고, 23행을 살펴보면 좌하단 좌표가 우상단 좌표보다 크다는 것을 알 수 있다. 이것은 분명히 잘못된 접근으로 위와 같은 잘못된 접근을 할 경우, 사용자가 입력한 두 점의 좌표가 비정상적이기 때문에 프로그램이 제대로 실행될 수 없다. 즉, 위의 예제는 사용자가 발생시킬 수 있는 문제에 대해 전혀 고려하지 않은 좋지 못한 프로그램이라고 할 수 있다. 따라서 이 프로그램은 정보 은닉을 도입하여 멤버 변수로의 제한된 접근만 허용하여 잘못된 값이 저장되지 않도록 해야 하고, 사용자가 실수를 했다면 실수를 가급적 쉽게 찾아낼 수 있도록 수정되어야 한다.

 

Point.cpp
class Point {
private: /* private으로 선언되었습니다. */
	int x, y;

public:
	/* 점의 x좌표, y좌표를 초기화하는 InitMembers 함수  */
	bool InitMembers(int xpos, int ypos) {
		if (xpos < 0 || xpos > 100 || ypos < 0 || ypos > 100) {
			cout << "잘못된 범위의 x좌표 또는 y좌표를 전달했습니다.\n";
			return false;
		}

		x = xpos;
		y = ypos;
		return true;
	}

	/* 점의 x좌표를 반환하는 GetX 함수 */
	int GetX() const {
		return x;
	}

	/* 점의 y좌표를 반환하는 GetY 함수 */
	int GetY() const {
		return y;
	}

	/* 점의 x좌표를 재설정하는 SetX 함수 */
	bool SetX(int xpos) {
		if (xpos < 0 || xpos > 100) {
			cout << "잘못된 범위의 x좌표를 전달했습니다.\n";
			return false;
		}
		x = xpos;
		return true;
	}

	/* 점의 y좌표를 재설정하는 SetY 함수 */
	bool SetY(int ypos) {
		if (ypos < 0 || ypos > 100) {
			cout << "잘못된 범위의 y좌표를 전달했습니다.\n";
			return false;
		}
		y = ypos;
		return true;
	}
};

 위의 예제는 정보가 은닉된 Point 클래스로, 먼저 public으로 선언되었던 멤버 변수를 private으로 선언하여 멤버 변수에 임의로 잘못된 값을 저장할 수 없도록 했다. 즉, 멤버 변수 x와 y를 은닉하였다. 대신에 멤버 변수에 값을 간접적으로 저장하는 InitMembers, SetX 그리고 SetY 멤버 함수를 정의하여 0 미만 100 초과 값이 전달되면 잘못된 범위의 값이 전달되었다는 메세지와 함께 멤버 변수에 저장되지 않도록 하였다.

 

Rectangle.cpp
class Rectangle {
private: /* private으로 선언되었습니다. */
	Point upRight;
	Point downLeft;

public:
	/* 두 점으로 직사각형을 초기화하는 InitMembers 함수 */
	bool InitMembers(const Point& ur, const Point& dl) {
		if (ur.GetX() < dl.GetX() || ur.GetY() < dl.GetY()) {
			cout << "잘못된 좌표를 전달했습니다.\n";
			return false;
		}

		upRight = ur;
		downLeft = dl;
		return true;
	}
	void DrawRectangle() const {
		cout << "우상단: " << "[ " << upRight.GetX() << ", " << upRight.GetY() << " ]\n";
		cout << "좌하단: " << "[ " << downLeft.GetX() << ", " << downLeft.GetY() << " ]\n";
	}
};

 위의 예제 역시 정보가 은닉된 Rectangle 클래스로, public으로 선언되었던 멤버 변수를 private으로 바꾸었고 멤버 객체를 초기화하기 위해 InitMembers 멤버 함수를 정의하였다. 이 함수는 우상단 좌표와 좌하단 좌표를 검사하여 두 점의 위치가 뒤바뀐 경우, 잘못된 좌표가 전달되었다는 메세지를 보내도록 하였다.

 이렇게 모든 클래스에서 멤버 변수의 접근을 제한하고 멤버 함수를 정의하여 초기화 성공과 실패에 따라 true 또는 false를 반환하도록 하였다. 이것은 초기화에 따라서 그에 상응하는 조치를 취할 수 있도록 개선한 것으로 이전보다 정보 은닉을 잘 지킨 코드라고 할 수 있다.

 

1.2 액세스 함수

 앞선 Point.cpp 예제를 살펴보면 GetX, SetX와 같이 멤버 변수 앞에 Get, Set을 붙여 정의된 멤버 함수가 있는데, 이것을 가리켜 엑세스 함수(Access Function)라고 한다. 엑세스 함수는 private으로 선언된 멤버 변수를 클래스 외부에서 접근하기 위해 정의된다. 하지만 클래스에 멤버 함수로써 정의되었지만 실제로 호출되지 않는 경우도 많은데, 이것은 프로그램을 설계하는 동안 가까운 미래에 사용될 것으로 판단되는 함수를 미리 멤버 함수로 포함시켜놓기 때문이다.

/* 액세스 함수 Getter, Setter */
void GetVariableName() const {
	return variableName;
}

void SetVariableName() {
	statements;
}

 

[한줄 요약] 액세스 함수
1. Getter는 멤버 변수의 값을 읽기 위해 사용한다.
2. Setter는 멤버 변수의 값을 쓰기 위해 사용한다.

 

1.3 const 함수

 앞서 const 참조자와 마찬가지로 const 함수는 다음과 같은 두 가지 속성을 가지고 있다. const 함수를 적절하게 사용하여 사용자의 코드를 더 안전하게 보호할 수 있다.

① const 선언된 멤버 함수는 멤버 변수의 값을 변경할 수 없다.

 멤버 함수를 const로 선언하면 멤버 함수에서 멤버 변수에 접근하여 그 값을 변경할 수 없다. 즉, 멤버 변수의 값을 변경하면 컴파일 오류가 발생하는데, 이러한 const 선언은 사용자가 잘못된 접근을 하지 못하도록 예방하는 역할을 한다.

 

② const 선언된 함수는 const로 선언된 함수 외에 호출할 수 없다.

const로 선언되지 않은 함수는 멤버 변수의 값을 변경하지 않더라도 변경할 수 있는 가능성을 갖고 있는 함수이다. 따라서 const로 선언된 함수는 const로 선언된 함수를 제외하고 다른 비const 함수를 호출할 수 없다.

 

IntPrinter.cpp
class IntPrinter {
private:
	int A, B, C;

public:
	IntPrinter(int A, int B, int C) : A(A), B(B), C(C) { }

	void PrintA() const {
		A = 10;	/* 컴파일 오류! const 함수는 멤버 변수의 값을 변경할 수 없습니다. */
		cout << A << endl;
		PrintB();	/* 컴파일 오류! const 함수는 const 함수 외에 호출할 수 없습니다. */
		PrintC();
	}

	void PrintB() {
		cout << B << endl;
	}

	void PrintC() const {
		cout << C << endl;
	}
};

 위의 예제를 살펴보면 IntPrinter 클래스에 세 멤버 변수를 출력하는 PrintA 멤버 함수가 8행에 정의되어 있다. 이때 PrintA 함수는 const로 선언되어 있어 const 함수의 규칙을 따르지 않은 9행과 11행에서 컴파일 오류가 발생한다. 9행은 멤버 변수 A의 값을 10으로 변경하려고 하는데, 멤버 변수의 값을 변경하지 못한다는 규칙에 어긋나 컴파일 오류가 발생한다. 또한 11행은 PrintB 함수가 멤버 변수의 값을 변경하지 않는 함수일지라도, 변경할 가능성을 갖고 있는 비const 함수이므로 규칙에 어긋나 컴파일 오류가 발생한다.

 

[한줄 요약] const 함수
1. 멤버 변수의 값을 변경할 수 없다.
2. const로 선언된 함수 외에 호출할 수 없다.

2. 정보 은닉과 const 문제 풀이

 

열혈 C++ 프로그래밍 | 문제 04-1 | 정보 은닉과 const

문제 1 Chapter 03에서 제시한 과일장수 시뮬레이션 예제 FruitSaleSim1.cpp에서 정의한 두 클래스의 멤버 변수는 private으로 선언되어 있다. 그러나 다음 조건을 유지할 수 있는 장치는 아무것도 되어있

continue96.tistory.com

 

댓글