본문 바로가기

캡슐화, 접근 지정자, 접근 함수 (Encapsulation, Access Specifiers, Access Functions) 본문

💘 C++/객체지향의 기초

캡슐화, 접근 지정자, 접근 함수 (Encapsulation, Access Specifiers, Access Functions)

Hyonii 2022. 8. 24. 16:18

캡슐화 , 접근 지정자 , 접근 함수

크고 뛰어나고 복잡한 기능을 가진 프로그램을 개발하려면 어쩔 수 없이 내용이 복잡해진다.

많은 variable, function, class 들을 정의해서 사용하게 된다.

복잡해 보이는 것들을 단순해 보이도록 깔끔하게 정리를 잘하고

각각의 모듈별로 조립, 분리를 잘하는 사람이 로그래밍을 잘 한다고 생각하는 게 일반적인 추세이다.

최근 소프트웨어 프로그래밍 기법은 재활용을 강조하는 방식으로 가고 있고 오픈소스를 많이 사용하고 있다.

그래서 프로그래밍을 할 때 복잡한 것들을 캡슐로 싸서 정리를 하는 것처럼 캡슐화하는 것이 중요한 개념 중 하나이다.

이번 포스팅에서는 캡슐화 Encapsulation 개념에 대해서 접근 지정자, 접근 함수와 함께 설명 드리겠다.


먼저 struct로 날짜를 정의한다고 생각해보자.

 

month, day, year 이렇게 세 가지가 있는데 멤버라는 의미로 앞에 m_를 붙여놓았다.

struct에서는 보통 m_를 안 붙이기는 하지만 객체지향 배우는 중이니까 붙임.

이때, Date를 초기화하려면 여러 가지 방법을 사용할 수 있다.

 

이렇게 uniform initialization할 수도 있고

uniform initialization을 사용하지 않고 멤버를 각각 초기화해줄 수도 있다.

더 편한 방법은 추후 설명드리겠다.

 

이때 struct를 class로 바꾸면

 

에러들이 뜬다.

이 에러들은 건드리지 말라는 의미다.

지금 보면 today.m_month , today.m_day, today.m_year 얘네는 main 함수 안에서 접근하고 있다.

그리고 class에 있는 m_month, m_day, m_year는 class Date 안에 정의가 되어있다.

그러니까 class로 만드는 순간

 

똘똘 싸놓고 못 건드리게 막아버린 것이다.

"얘네들은 내 거니까 main에서는 건들지 마! 나만 건드릴 수 있어" 이런 뉘앙스다.

그래서 에러가 나는 것이다.

 

마우스 가져다 대면 line 9에 선언된 m_month에 접근할 수 없다고 뜬다.

초보자들은 struct와 class가 비슷해 보여서 난감해 할 수 있다.

지금 이 상황을 해결하려면

 

여기다가 public 하고 : 콜론을 찍어 주면 된다.

이것을 access specifier 접근 지정자라고 부른다.

class 안에 있는 멤버들을 밖에서 접근할 수 있게 해 줄 건지, 없게 해 줄 건지를 결정하는 것이다.

 

access specifier에는 세 가지 종류(public, private, protected)가 있는데

그중 첫 번째 public은 지금 나오면서 접근할 수 있게 되었고

두 번째로 private이 있다.

 

private이라고 치면 다시 아까처럼 오류가 뜬다.

private은 막는 것을 기본으로 하고 있다.

public이라고 따로 안 써주면 그냥 private이 기본이다.

private을 public으로 바꾸면 공개가 된다.

 

public으로 바꿔봄

외부 다른 영역에서 접근을 할 수 있는 것이다.

 

세 번째로 protected라는 게 있는데 이건 '상속'을 공부한 다음에 포스팅하겠다.

 

이때 private으로 막아버리면 외부에서 값을 어떻게 바꿀지 궁금해 할 수 있다.

이럴 때에는 access function을 만들어줘야 한다.

"접근을 하려면 나를 통해서만 해"

"얘랑 얘기하려면 나를 통해서 얘기해" 이런 뉘앙스다.

 

물론 public으로 해놓고 마음껏 코딩하는 방법도 있다.

처음에 prototyping 할 때에는 다 public으로 해놓고 짜는 게 어떻게 보면 좋은 방법이 될 수도 있다.

하지만 오픈소스로 작업을 하거나 상업용 소프트웨어에 들어가는 코드일 경우 엄격하게 구분해주는 게 더 좋다.

다른 사람하고 일을 같이 한다거나 프로그램이 커질게 확실한 경우에는 좀 귀찮고 일이 많더라도

access function들을 그때그때 구현하는 게 장기적으로 보았을 때 좋다.

그 이유에 대해서는 밑에서 예제로 보여드리겠다.

 

encapsulation을 잘하기 위해서 일단 private으로 감추겠다.

 

혹은 아예 안 써도 된다.

오픈 소스 보면 private이 안 보이는 경우도 꽤 많다.

그 이유는 기본이 private이기 때문이다.

 

그래서 이 변수들을 통하려면 통할 수 있는 함수 access function을 만들어줘야 한다.

 

나를 통해서 얘기해라라는 것은 나는 공개를 하고 안쪽에 있는 것은 감춘다는 의미니까

함수 자기 자신은 public 이어야 한다.

 

이런 식으로 정의를 하면 꽤 깔끔해진다.

 

이때 의아해 할 수 있는 것은

setData 함수는 public이다.

그런데 다른 멤버 variable(m_month, m_day, m_year)들은 private이다.

같은 class의 멤버라면 자기 자신은 public이더라도 private 멤버들에 접근할 수 있다.

encapsualtion을 해주려면 누군가 외부와 소통을 하는 존재가 있어야 한다.

지금 일종의 access function을 하나 만든 것이다.

 

그리고 또 하나 의구심이 생기는 것은

초기화할 때 uniform initialization 하듯이 좀 편하게 할 방법은 없을까?

struct를 initialization 할 때는 편했는데 그렇게 할 방법이 없나? 생각할 수 있다.

그거는 곧 생성자를 만들면서 배우게 될 것이다.

일단은 encapsulation 공부하는 겸 이렇게 access function을 만들어 보았다.

 


다음으로 생각해 볼 수 있는 게

 

지금 이 setDate 함수는 통째로 month, day, year 세 가지를 한꺼번에 입력받아서 멤버 값들을 바꿔줄 수 있고

 

이렇게 각각 함수로 구현할 수 있다.

 

이렇게 써먹을 수 있음

그렇다면 값을 set 할 수는 있는데 출력하고 싶은 경우에는 어떻게 해야 할까?

 

이렇게 하면 private으로 막혀 있기 때문에 당연히 오류가 난다.

그래서 보통 get 함수도 만들어 준다.

 

m_month에 직접 접근을 하는 게 아니라 이런 식으로 함수를 통해서 접근하도록 해준다.

 

이때 value로 return을 할 경우에는 복사되니까 안 좋다고 생각을 해서

레퍼런스를 return 하려고 시도하는 경우가 있다.

 

여기서 레퍼런스는 반환값이 참조인 형태

이렇게 레퍼런스를 return 하는 경우도 있는데

보통 이렇게 멤버 variable의 get access function을 만들 때에는 앞에 const로 막아버린다.

 

getMonth()에서 m_month의 값을 바꾸지 못하게 const로 막아버리고,

만약 m_month의 값을 바꾸고 싶다면 

 

이 setMonth 함수를 통하도록 깔끔하게 정리하는 것이 일반적이다.

 

보통 get이 들어가는 함수들을 getters.

set이 들어가는 함수들을 setters라고 부르기도 한다.

 


그다음으로 setDate로 날짜를 하나 만들고 그 날짜를 복사하고 싶을 때에는 어떻게 해야 할까?

 

copy를 하고 싶은데 이렇게 하나하나 해주기는 매우 귀찮아진다.

 

그래서 아예 이런 식으로 내가 복사하고자 하는 class의 instance variable 하나만 딱 넣어버리면 깔끔해질 수 있다.

복사해온다는 개념을 살리기 위해서 copyFrom이라는 함수를 하나 만들어보자.

 

이렇게 만들 수 있다.

물론 얘네를 메모리를 통째로 쉽게 복사하는 방법도 있다.

더 쉬운 방법도 있는데 지금 굳이 수동으로 복사한 이유는

보통 class를 만드는 이유가 단순한 것보다는 좀 더 복잡한 것들을 위해서 만든다.

그때 실수하지 않기 위해서 수동으로 많이 작성하기도 한다.

 

아무튼 copyFrom(today); 를 넣어주면 복사가 된다.

 


같은 class의 다른 instance에 있는 멤버도 접근이 가능하다.

original의 m_month는 original 안에서 private이다.

그런데 다른 instance의 member function에서 마음대로 접근을 하고 있다.

 

보면 지금 today와 copy는 Date라는 같은 class의 instance이다.

하지만 같은 class로부터 만들어지긴 했지만

today와 copy는 메모리 주소가 서로 다르고 내용도 다르기 때문에 서로 다른 존재이다.

 

instance 얘기가 갑자기 나오니까 복잡하다고 생각하실 수 있는데

예를 들어 int i, j; 가 있을 때 때 i와 j는 다르다와 같은 느낌이다.

 

그래서 다른 메모리에 있는 애들인데도 불구하고

같은 class로부터 나왔으면 가져다 쓸 수 있다.

copyFrom이라는 함수에서

 

같은 class의 다른 instance에 들어있는 멤버도 접근이 가능하다.

 

setDate 함수는 자기 자신은 public이지만 같은 class안에 들어있다는 이유로

private으로 선언이 되어있는 m_month, m_day, m_year에 접근할 수 있었다.

copyFrom 함수도 마찬가지이다.

 

어차피 같은 클래스 안에 정의되어있는 같은 멤버니까 original이라는 변수는 똑같이 Date의 instance이다.

그러니까 original.m_month를 접근할 수 있는 것이다.

 

이걸 설명해서 오히려 더 복잡하게 만들었을 수 있다;

같은 클래스니까 같은 멤버라서 당연히 되는 거 아니야?라고 생각하는 게 오히려 더 쉬울 수 있다.

 


encapsulation 할 때 getter, setter를 구현하는 이유 

얘네를 public으로 선언했을 때

 

이렇게 멤버 variable들이 코드 전반에 걸쳐서 여러 군데 사용되고 있다고 생각해보자.

그리고 어떠한 이유 때문에 이 변수명을 바꿔야 하는 경우가 생겼다고 가정해보자.

 

그러면 이렇게 변수명을 바꿈과 동시에 오류가 많이 생긴다.

 

클래스 밖에서도 바꿀 것들이 생기게 된다.

 

지금 예제에서는 main에 today.m_month 하나밖에 없지만

구현을 하다 보면 class가 class를 사용하는 경우가 많아진다. 그래서 복잡해진다.

자동으로 바꿔주는 Rename 기능을 쓰면 되지 않냐고 생각할 수 있다.

 

이걸 쓰면 비교적 잘 잡아주는데

나중에 좀 더 복잡한 것을 하다 보면 class가 수백 개 생긴다.

그러면 visual studio도 완벽하게 잡아주지를 못한다. 힘들어진다.

그래서 애초에 얘를 private으로 잡아놓고

 

access function만 건드릴수 있게 해 놓으면

 

이렇게 변수명을 바꿀 때 class안에서 해결이 가능하다.

 

얘네들을 encapsulation을 안 하고 전부 public으로 해놓고 밖에서 막 사용하게 돼버리면

이 class 안에서 끝나는 게 아니라 다른 곳에도 돌아다니면서 막 고쳐야 한다.

 

그리고 rename 해주는 비주얼 스튜디오의 기능도 완벽하게 작동하지 않고

결국은 손으로 다 뒤져서 봐야 하는 힘든 경우가 생길 수 있다.

 

 

Comments