struct big_integer { char *digit; int length; char sign; struct big_integer* add(struct big_integer lhs, struct big_integer rhs); struct big_integer* subtract(struct big_integer lhs, struct big_integer rhs); struct big_integer* multiply(struct big_integer lhs, struct big_integer rhs); struct big_integer* divide(struct big_integer lhs, struct big_integer rhs); };


지난 시간에 이어 포스팅을 하겠다. 지난 시간에는 C 구조체를 이용해서 위와 같은 big_integer 구조체에 필요한 맴버변수들과 관련 함수들을 묶어서 캡슐화를 할 수 있다는 것 까지 진행했다. 이렇게 확장된 구조체를 C++에서는 클래스라고 부른다. 따라서 다음과 같이 나타내어질 수 있다.

class big_integer { char *digit; int length; char sign; struct big_integer* add(struct big_integer lhs, struct big_integer rhs); struct big_integer* subtract(struct big_integer lhs, struct big_integer rhs); struct big_integer* multiply(struct big_integer lhs, struct big_integer rhs); struct big_integer* divide(struct big_integer lhs, struct big_integer rhs); };


위 구조체와 달라진 점은, struct가 class로 바뀌었다는 것 뿐이다. 실제로 C++에서 구조체와 클래스의 문법적인 차이는 매우 미미하다. 하지만 단지 구조체에 함수를 포함시켰다는 것의 작은 의미가 아니라, 절차지향 프로그래밍에서 객체지향 프로그래밍이라는 패러다임의 변화가 함축되어 있는 그러한 부분이다. 따라서 이러한 상징성을 위해서 class라는 새로운 이름을 부여한 것이다.


게다가 실제로 객체지향 프로그래밍을 할 때, 다른 객체지향 개념이 내재된 언어에서 구조체라는 단어는 쓰지 않고 모두 클래스와 객체라고 부른다. 그러면 문법적인 부분에서 C++의 구조체와 클래스의 차이는 어떠한 것이 있을까?


이를 알기 위해선 우선 접근지정자(Access Modifier)와 정보 은닉(Data Hiding)에 대하여 알아야 한다.


객체지향 패러다임에서는 절차지향 패러다임 보다 코드의 모듈화와 재사용성에 많이 관심을 가지면서도, 사람에 의한 실수들을 줄이는 방향으로 발전했다.


객체지향 패러다임에서는 남이 작성한 클래스와 모듈들을 쉽게 재사용하도록 하면서 생산성을 늘렸다. 남이 미리 작성한 클래스들의 내부적인 복잡한 로직은 알 필요가 없도록 추상화되어 숨겨져있고, 어떠한 상황에서 어떠한 함수를 호출해야 하는지 정도만 알면 된다.


하지만 또 다른 관점으로 보면 다른 누군가가 작성한 그 클래스를 사용하는 개발자는 그 클래스의 내부 로직에 대해 잘 모를 것이고, 내부의 중요한 값들을 잘못 건드렸다가 내부적 로직이 꼬이거나 버그를 유발할 수도 있을 것이다.


예를 들어서 big_integer 클래스를 사용하는 누군가가 big_integer.length += 5; 와 같은 코드를 추가하게 된다면 어떠한 일이 발생할까? 실제 자릿수의 값들은 digit이라는 char 포인터 변수가 가리키는 곳에 문자열 형태로 있을 것이고, 이 때 문자열의 길이는 length라는 변수로 계산이 될 것이다. 따라서 허용되지 않은 메모리에 접근하게 될 수도 있다. 그러므로 big_integer 클래스를 사용하는 개발자는 length라는 변수의 값을 읽는 것은 가능하게 하되, length의 변수의 값을 마음대로 바꾸도록 하면 안된다. length의 값은 대입, 덧셈, 뺄샘, 곱셈, 나눗셈 등의 연산으로 인하여 자연스럽게 변화되는 경우에만 변해야 한다. 이럴 때 적용할 수 있는 것이 접근지정자이다.

class big_integer {
private:
	char *digit;
	int length;
	char sign;
	struct big_integer* add(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* subtract(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* multiply(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* divide(struct big_integer lhs, struct big_integer rhs);
};

private라는 이름의 접근 지정자를 설정함으로써, private 접근지정자 밑의 맴버변수와 메소드들은 모두 외부에서 접근이 불가능하도록 변경되었다. 하지만 이렇게 되는 경우는 메소드들도 접근할 수 없게 되므로 좀 곤란해진다. 따라서 메소드들은 외부에서 접근이 가능하도록 변경해 보았다.


class big_integer {
private:
	char *digit;
	int length;
	char sign;
public:
	struct big_integer* add(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* subtract(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* multiply(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* divide(struct big_integer lhs, struct big_integer rhs);
};

이렇게 해서 메소드들은 외부에서 접근이 가능하지만, 멤버변수들은 그렇지 않도록 설정되었다. 그러면 big_integer의 자릿수를 length 변수를 통해서 알아내고 싶다면 어떻게 해야할까?


length 변수에 대한 getter 함수를 지정해주면 된다. 다음과 같은 형태가 될 것이다.

class big_integer {
private:
	char *digit;
	int length;
	char sign;
public:
	struct big_integer* add(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* subtract(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* multiply(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* divide(struct big_integer lhs, struct big_integer rhs);
	int get_length() {
		return length;
	}
};

위와 같이 get_length 함수를 정의해주면, 외부에서 length 멤버변수의 값을 알아야 할 때, get_length 함수를 호출하면서 간단하게 값을 알아낼 수 있다.

일반적으로 클래스의 메소드들을 디자인할 때, 이러한 점에 입각해서 멤버변수들은 모두 private 접근지정자를 설정하고, 메소드들은 public 접근지정자를 설정하는 것이 일반적이다. 그리고 접근해야 할 필요가 있는 멤버변수들에 대해서는 각각 getter 함수와 setter 함수를 지정한다. 만약 setter 함수를 지정하게 된다면 다음과 같은 모습이 될 것이다.


class big_integer {
private:
	char *digit;
	int length;
	char sign;
public:
	struct big_integer* add(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* subtract(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* multiply(struct big_integer lhs, struct big_integer rhs);
	struct big_integer* divide(struct big_integer lhs, struct big_integer rhs);
	int get_length() {
		return length;
	}
	void set_length(int length) {
		this->length = length;
	}
};

하지만 이 big_integer 클래스에서는 length의 값을 임의로 바꾸면 안되므로, set_length 함수가 없는 것이 낫다. 그리고 만약 set_length 함수를 사용한다고 하더라도 저렇게 간단하게 값만 대입하는 방식이 아닌, 입력받은 값이 정당한 값인지 확인하는 과정이 필요할 것이다. 예를 들면 length의 인자에 음수가 들어올 경우 0값을 대입한다는 식으로 말이다.


이렇게 맴버변수와 같이 잘못 건드릴 수 있는 값들을 접근지정자 등을 통해서 외부에서 잘못 바꾸지 않도록 안전장치를 만드는 것을 정보 은닉(Data Hiding)이라고 한다.


이 정보 은닉을 객체지향 프로그래밍의 특징의 한 카테고리고 분류해서 넣는 경우도 있지만, 일반적으로는 저번 포스팅에서 언급했던 "캡슐화"의 특징 중 하나로 보는 경우가 많다. 따라서 "캡슐화"의 특징은 두개로 나뉜다고 볼 수 있다.

1) 관련된 데이터(멤버변수)와 동작(메소드)를 한 곳에 묶어음

2) 외부에서 몰라도 되거나, 몰라야 하는 정보들을 자유롭게 접근하지 못하도록 제한하고 은닉함 (정보 은닉)


이제 C++ 언어에서 구조체와 클래스의 문법적 차이점을 알아보자. 이 두 개의 문법상 차이는 기본 접근 지정자(Default access modifier)가 구조체의 경우 public이고, 클래스의 경우 private라는 것이다. 다시 말하면, 접근 지정자를 설정하지 않을 경우 구조체는 모두 public이고, 클래스는 모두 private이다.

------------------------------------

다음 포스팅에서 이어진다.

+ Recent posts