Prototype Ghost
귀신일지
Prototype Ghost
전체 방문자
오늘
어제
  • 분류 전체보기 (29)
    • Hacking (1)
      • CTF (0)
      • Wargame (0)
      • Review (1)
    • C (12)
      • Crypto (12)
    • C++ (4)
    • Python (1)
    • Swift (1)
    • Blockchain (9)
      • Solidity (9)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 블록체인
  • Blockchain
  • 정보보안
  • ethereum
  • 접근제어
  • 암호화
  • 비트코인
  • Bitcoin
  • 프로그래밍
  • 리눅스
  • 솔리디티
  • c언어
  • 복호화
  • C++
  • 이더리움
  • 보안
  • solidity
  • 암호화폐
  • openssl
  • C

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Prototype Ghost

귀신일지

C++ 두번째, 클래스(Class) 기본 개념과 접근 제어 지시자
C++

C++ 두번째, 클래스(Class) 기본 개념과 접근 제어 지시자

2022. 2. 23. 01:01

안녕하세요 여러분 귀신입니다.

글이 조금 늦었는데 공부를 안한건 아니고

이 블로그가 그저 제가 공부했던걸 복습함과 동시에

후에 잊어버렸을 때 찾아보려고 만드는 용이라 그런지

알고 있는 내용을 굳이해야하나 하는

그런 고민이 있었습니다.

그래도 꼭 짚고 넘어갈건 짚고 넘어가야겠다는 생각에

오늘은 간단하게 클래스에 대해서 알아보려고 합니다.

바로 시작하겠습니다.

 

1. 클래스에 대하여


  클래스에 대하여 간단하게 짚고 넘어가겠다. 절차지향인 C언어를 공부하다 객체지향 C++을 처음 배울 때면 다들 당황하고는 합니다. 그 이유는 여러가지가 있죠. 일단 C++이라 하니 C언어랑 비슷할 줄 알고 겁없이 책을 펼쳤다 그대로 덮고 싶다는 생각을 했을 수도 있고, 모든 것들을 클래스로 객체화하는 코드가 조금은 낯설 수도 있습니다. 그러나 저는 C++을 처음할 때 가장 이해하기 힘들었던 것은 바로 객체지향의 철학이었습니다.

"도대체 왜 모든 것들을 굳이 객체화해서 클래스로 만들지? 왜 언어 자체가 이렇게 코딩하도록 만들어진거야?" 하는 의구심이 초반에 C++을 하는 저에겐 큰 어려움이었습니다. 캡슐화 이런 언어들도 참 낯설었죠. 절차지향에 익숙해져버린 나머지 객체지향을 이해하지 못해서 객체지향은 옳지 못한거라고 머릿속으로 우겨본적도 있을 정도였습니다.

저한테는 그만큼 코딩을 하면서 중요한 점은 명분과 이유이기 때문입니다. 그리고 그렇게 그저 받아들이지 않고 끝까지 의심하고 질문하며 점점 답에 가까워졌고 조금은 시간이 들었지만 전혀 그 시간이 아깝진 않습니다. 끝까지 고민했기 때문에 100%는 아니더라도 이젠 왜 C++ 코드는 이렇게 짜야하는지 느낌이 왔거든요

코딩을 잘 못하는 친구들한테 제가 항상 하는 말이 있습니다. 그냥 외우는 것이 아니라 이런게 왜 쓰이는지 그 이유를 알면 암기는 따라오는 거고 너가 정말 필요할 때 적재적소에 알맞게 사용할 수 있게된다고 말이죠. 이 글을 읽는 여러분들도 지금까지 그냥 암기로 코드를 짰다면 앞으로는 이러한 기능이 왜 생겼는지, 왜 쓰는지를 조금 더 고민하면 실력이 일취월장할 것이라고 자부합니다. 

 

  위에는 제가 그냥 하고 싶은 말이었구요, 그래서 어쨌든 객체지향언어인 C++에서 당연 가장 중요한 것은 역시 Class입니다. 이 녀석을 사용해야 객체를 만들 수 있기 때문이죠. 우선 앞서 말했듯, 객체지향의 개념, 그리고 Class를 처음부터 완벽하게 이해하기란 사람마다 다르겠지만 쉽지않습니다. 그래서 처음 보통 클래스를 이해시키기 위해 다들 이런말을 하곤 합니다. "클래스는 C언어의 구조체안에 멤벼 변수 뿐 아니라 멤버 함수까지 포함한 개념이다." 맞는 말입니다.

우선은 이렇게라도 이해해야합니다. 물론 클래스를 쓰는 이유는 이 뿐만 아니라 더 많은 것들을 가능하게해서이지만 그건 추후에 더 많은 코드를 접해보면 이해가 될 것입니다. 그리고 또하나 흔히 "틀"과 같다고 클래스를 묘사하기도 합니다. 틀은 형태를 만들어 놓으면 그때그때 편리하게 찍어낼 수 있다는 장점이 있습니다. 클래스 또한 마찬가지입니다.

한번 만들어 놓고 그때그때 편리하게 객체를 생성하는 것입니다. 이제부터 코드로 살펴보겠습니다.

 

  그러면 유튜브 이것이 C++이다에서 설명해 준 구조체에서 클래스로의 변화 과정에대해 먼저 살펴보겠습니다.

우선 기존의 구조체를 먼저 확인해봅시다.

  C언어를 하셨다면 익숙한 구조체를 이용한 코드입니다. CTest라는 구조체의 멤버변수 nData가 있고

이 멤버변수를 출력하는 printCTest()라는 함수가 있습니다. 15번 라인을 보면 구조체 주소를 넘겨 기능하는 것을 볼 수 있습니다. 그러나 여기서 문제점이 하나 존재합니다. 예를 들어 구조체에서 멤버변수에 접근할 때 보통 test.nData와 같이 사용합니다. 이는 nData가 구조체의 멤버라는 사실을 명확히 보여줍니다. 그러나 전역함수로 기술한 printCTest()

15번 라인과 같이 사용하면 사실 CTest라는 구조체와 연관되어있다는 느낌이 없습니다. 물론 함수이름으로 그나마 표현하기는 했으나, 명확하지는 않습니다. 그래서 다음 코드와 같은 방법을 생각해냅니다.

함수포인터 활용

  6번 라인을 보면 멤버변수에 함수포인터를 선언한 것을 볼 수 있습니다. 원칙적으로 함수포인터도 변수이기 때문에

멤버로써 기술할 수 있습니다. 그 후 14번 라인을 보면 printCTest함수로 함수포인터를 초기화하였습니다.

이와 같이 코딩한다면 15번 라인 test.printMemberData(&test) 처럼 . 을 이용해 함수를 이용할 수 있고 이는 아까 그 전의 코드보다 이 구조체에 포함되어있는 함수다라고 느낌을 줄 수 있습니다. 즉 아까보다 훨씬 구조체와 직접 연관되어 있음을 표현할 수 있습니다. 실제로 리눅스 코드같은 경우는 위와같이 코딩되어있는것으로 알고 있습니다. 그러나 아직 완벽하진 않습니다. 이유가 뭘까요? 바로 매개변수 입니다. 매개변수에 &test와 같이 구조체의 주소를 넘깁니다. 여기서 이상한 느낌이 듭니다. 왜냐하면 printMemberData는 test에 소속되어있는 멤버인데 매개변수로 test주소를 넘기기 때문입니다. 쉽게 말해 자기 집의 주소를 모르는 느낌입니다. 자신이 소속되어있는 곳의 주소를 파라미터로 넘겨야 한다는 사실이 어색한 것입니다. 그래서 C++에서는 클래스라는 것이 생겼고 이러한 단점이 고쳐집니다.  

클래스를 사용

  위의 코드는 C++에서 클래스를 이용한 예시입니다. 클래스의 설명은 다음 코드에서 하기로 하고 호출자 쪽을 보겠습니다. 16번 라인을 보면 test.printData()와 같이 기술되어있는 것을 확인할 수 있습니다. 이는 구조체의 주소를 넘기는 코드와 달리 어색함이 없습니다. 즉 완전히 함수가 하나의 멤버라는 느낌이 강하게 듭니다. 물론 실제로는 히든 파라미터로 보이지 않게 자기 자신의 주소가 넘어간다는 사실은 알고 계시면 좋을 것 같습니다. 우리 눈에 보이지 않게요. 이렇게 해서 함수라는 것은 구조체에서 변화하여 클래스로 가서 비로소 완전히 하나의 멤버로써 인정받을 수 있게 됩니다. 지금까지는 우리가 흔히 설명하는 클래스는 구조체에서 함수가 추가된 것이다라는 쉬운 설명을 조금 깊게 파고 들어봤습니다.

그럼 지금부터는 클래스의 구조에 대해서 조금 더 알아보는 시간을 가져보도록 하겠습니다.

기본적인 클래스의 구성

  자 아주 간단하게 위의 코드를 보면서 클래스가 대충 이런 구조구나 하고 짚고 넘어가보겠습니다. 우선 클래스는 class라는 키워드로 선언하고 class키워드 옆에 이 클래스의 이름을 정의합니다. 여기서 typedef같은 키워드가 보이지 않는데요. 사용하지 않은 것이 아니라 사용할 필요가 없는 것입니다. 그 이유는 이제 구조체에서 typedef로 이름을 재선언했던 것처럼 할 필요가 없기 때문입니다. 그동안 C언어에서는 이름을 재선언함으로써 구조체 변수를 선언할 때 귀찮게 struct를 쓰는 행위를 방지할 수 있었는데요 클래스에서는 객체를 선언할 때 class라는 키워드를 붙이지 않아도 됩니다. 20번 코드를 보면 그냥 클래스의 이름만 기술하면서 객체를 선언한 것을 알 수 있습니다. 혹시 객체라는 말, 혹은 인스턴스라는 말이 조금 어렵게 느껴지신다면 검색을 통해 어느정도 느낌을 찾고 오시는 것을 추천드립니다. 제가 하나 설명드리자면 객체는 보통 광범위하게 어떤 클래스의 객체처럼 용어를 사용하고 인스턴스는 CTest클래스의 인스턴스와 같이 보다 특정 클래스의 객체다 라는 느낌으로 사용합니다. 현실에서 거의 구분하지 않고 사용하기도 합니다. 6번 라인에는 멤버 변수가, 그리고 13번 라인에는 멤버 함수가 정의되어있네요.

 

  넘어가서 5번줄에 private, 7번라인에 public 이라는 키워드가 보이는데요 이는 접근제어지시자 라고 불립니다. 이는 외부에서 클래스 내부의 멤버변수나 함수에 대한 접근을 제어하는 키워드입니다. 이게 굉장히 중요한데요. 앞으로 계속 사용할 것이기 때문에 짚고 넘어가겠습니다. 우선 접근제어지시자중 하나인 protected라는 키워드가 있는데 이는 거의 사용하지 않을 뿐더러 저도 사용하지 않기때문에 그냥 넘기겠습니다. 하나만 말씀드리면 그냥 상속관계에 있는 클래스만 접근 가능한 키워드입니다. 아마 많이 사용하게 될 것은 private과 public인데요. 한가지 말씀드릴 건 어떠한 접근제어지시자도 쓰지 않으면 자동으로 멤버는 자동으로 private 처리가 됩니다. 쉽게 말해  private은 생략 가능합니다. 그럼 무슨차이가 있는지 살펴보겠습니다. 

 

2. 접근제어지시자


  private 키워드는 자기 클래스 내부에서만 접근가능합니다. 즉 외부에서의 모든 접근을 원천차단합니다. 그리고 위에서 설명했듯, private키워드는 생략가능합니다. 기본적으로 접근제어지시자를 기술하지 않으면 private으로 클래스가 간주하기 때문이죠. 실질적으로 멤버변수는 대부분 private으로 선언합니다. 코드를 봅시다.

  위의 코드를 보면 6번에는 접근제어지시자 public이 선언되었고 int nData멤버변수는 아무런 키워드 없이 선언되어있습니다. 다시 말씀드리지만 아무런 선언이 없으면 private으로 간주되기 때문에 현재 nData는 private입니다. 즉 nData는 외부에서 절대 접근할 수 없습니다. 접근할 수 없다는 것은 15번 라인에 주석처리문을 보시면 알 수 있습니다. 우리가 구조체에서 했던대로 test.nData 즉 . 을 이용해 멤버변수에 접근하였습니다. 그러나 저렇게 기술하면 바로 에러가 발생합니다. test의 멤버변수 nData가 private으로 선언되었기 때문에 외부인 메인함수에서의 접근을 허락하지 않기 때문입니다. 그러나 public으로 선언된 멤버함수(멤버함수는 다른말로 메서드라고 표현합니다.) printData()함수는 다릅니다. public은 어디에서나 접근을 허용한다는 의미이며 이는 다시말해 무조건 접근 가능하다고 보시면 됩니다. 즉 printData는 어디서나 호출할 수 있고 16번 라인을 보면 메인에서 호출했을 때 private으로 선언된 nData와 달리 정상작동 하는 것을 볼 수 있습니다. 여기서 하나 더 살펴볼 것은 바로 printData의 정의입니다. printData() 함수는 nData를 출력하는 기능을 합니다. 여기서 printData() 그리고 nData모두 CTest클래스의 멤버입니다. 따라서 같은 클래스의 멤버일 경우는 private으로 선언되어도 접근할 수 있고 printData()가 nData에 접근해도 아무런 에러가 발생하지 않는 것입니다.

여러분은 우선 편하게 변수는 private, 메서드는 public으로 설정한다고 생각하시면 됩니다.

 

  우선은 이정도만 알아도 접근제어지시자를 사용하는데 어려움은 없습니다. 정말 거의 대부분 변수는 private, 메서드는 public 으로 선언하기 때문에 헷갈릴 이유도 없습니다. 그러나 이를 사용하는 이유에 대해서는 짚고 넘어가겠습니다. 

접근제어지시자를 사용하는 이유를 정확히 알면 코드를 작성하는데 더 깊이있는 생각을 할 수 있기 때문이죠. 

제가 그랬습니다. 사실 C언어에서는 변수명, 함수명, 어느정도 암묵적인 약속? 들만 지키면서 유지보수에 대한 얘기를 할 수 있었습니다. 물론 보다 디테일한 것들이 있지만 일단 그렇습니다. 그러나 클래스는 유지보수측면에서 보다 더 신경써야 합니다. 아니, 신경쓰게 만들었습니다. 문법을 통해 강제로라도 미래의 문제를 사전에 제거한 것이죠. C++은 명확하게 코드를 작성한 사람과 그 코드를 사용하는 사람에 대해 구분해야합니다. 보통 클래스를 작성하면 만드는 사람, 메인함수에서 이러한 클래스를 사용하는 사람을 사용자라고 여깁니다. 

여러분들이 꼭 알아야 할 것은 클래스를 만든 사람은 당연히 메인 함수에서 이를 사용하는 사람보다 훨씬 더 그 클래스에 대해 잘 알고 있습니다. 그러나 사용자는 만든 사람에 비해 무지하기 때문에 혹시 모를 실수를 할 수 있습니다. 클래스를 만든 사람은 자신이 이 클래스를 잘 안다고 해서 미래에 누군가 사용할 사용자 또한 자신만큼 안다고 착각할 수 있지만 이는 엄연히 잘못된 생각입니다. 사용자는 절대 클래스를 작성자만큼 알 수 없고 실수할 가능성이 높습니다. 

여기서 접근제어지시자의 필요성이 부각됩니다. 

 

  제가 예를 들어보겠습니다. 우리 핸드폰에는 볼륨의 높낮이를 조절할 수 있습니다. 보통 핸드폰에서 0~100으로 볼륨을 올리고 내릴 수 있는데요. 제가 아까 위에서 멤버 변수는 private으로 한다라고 말한거 기억하시나요? 예를 들어 핸드폰이라는 클래스가 있고 이 클래스에서 스피커의 볼륨을 나타내는 멤버변수가 int volume 으로 선언되었다고 가정해 봅시다. 이 클래스를 만든 사람은 볼륨이 최소 0, 최대 100이라는 사실을 알고 있습니다. 이 클래스를 만든 사람을 A, 사용자를 B라고 하겠습니다. A는 자신이 클래스를 만들었기에 이러한 사실을 잘 알고, 볼륨을 0~100 실수없이 사용하였습니다. 이후 미래에 B라는 사용자가 핸드폰을 사용하게 되었습니다. 이 사용자가 만약 volume 변수에 직접 접근할 수 있다면 어떻게 될까요? B가 0~100이라는 범위를 무시할 가능성이 있습니다. 만약 B가 계속 볼륨을 올려 볼륨값이 100을 넘어 1000, 아니 10000처럼되면 어떨까요? 상상해보세요. 여러분들의 스마트폰 음량값이 10000됐다고요. 정상적으로 큰소리를 낼까요? 당연히 아닙니다. 스피커가 음량 출력이 너무 커져 찢어지거나 아예 작동하지 않을 수 있습니다. 또 만약 볼륨을 -1로 한다면 어떻게 될까요? 소리에 마이너스에 대한 개념이 있을까요? 이또한 분명 스피커가 오작동할 것입니다. 

이처럼 사용자가 외부에서 멤버변수에 직접적으로 접근하게 된다면 어떤 문제가 발생할지 알 수 없습니다. 이는 사용자의 문제가 아니라 사용자가 접근하게 하지 못한 작성자의 잘못입니다. 작성자, 쉽게 말하면 스마트폰을 만드는 사람은 절대 핸드폰을 구매한 사람이 볼륨값을 자유자재로 사용할 수 없게 해야합니다. 이와 같은 문제를 사전에 차단하기 위해, 멤버변수를 private으로 선언하는 것입니다. 절대 사용자가 접근할 수 없게 말이죠.

 

  그렇다면 메서드는 왜 public으로 선언할까요? 함수는 여러분들도 알다싶이 변수와 같은 어떤 특정한 값이 아닌 말 그대로 "기능"이라는 것을 기술합니다. 변수는 그냥 "스피커 볼륨 값" 이라면 함수는 "볼륨 키우기", "볼륨 낮추기" 와 같습니다. 어떠한 기능을 하게 하죠. 이 기능은 작성자가 스피커 볼륨은 위에 버튼을 누를때마다 5씩 올리게 할 수 있고 밑에 버튼을 누르면 5씩 낮추게 만들 수 있습니다. 그리고 이 버튼은 여러분들도 알다시피 사용자가 직접 누를 수 있는 버튼이죠. 그와 동시에 이 함수에서

"만약 볼륨값이 100이 되면 위에 버튼을 눌러도 볼륨을 올리지 않는다."

"만약 볼륨값이 0이 되면 밑에 버튼을 눌러도 볼륨을 내리지 않는다." 

와 같이 예를 들어 if문 등을 통해 특정 값에 도달하면 동작하지 않도록 할 수 있습니다. 즉 이렇게 만들어 놓으면 사용자가 버튼에 접근한다해도 문제가 발생할 가능성이 없어지는 것이죠. 다시말해 사용자가 볼륨값에 직접 접근하는 것이 아니라, 이를 기능으로써 버튼이라는 것으로 접근하게 한다면 훨씬 안전하게 스마트폰을 사용할 수 있게됩니다. 

즉 이처럼 변수에 직접 접근은 위험성이 크기때문에 변수는 private, 기능으로써 만들어 놓으면 보다 안전하기 때문에 함수는 접근을 허용하는 public으로 선언하게 되는 것이죠.

 

3. 마무리


  이해되셨나요? 여기서 여러분들이 알아야할 것이 바로 작성자는 미래의 사용자가 실수할 가능성을 사전에 제거해야 한다는 점입니다. 어떤것을 접근하게 하고 어떤것을 접근 못하게 해야 하는지 심도있게 고민하고 사용자의 실수 위험을 없애는 것이 현명하고 실력있는 개발자 입니다.  접근제어지시자의 "사용법"만 본다면 정말 간단하죠. 공식처럼 변수는 private, 메서드는 public. 그러나 이를 사용하는 "이유"는 그렇게 간단한 것이 아닙니다. 사용자가 안전하게 사용할 수 있게 하기 위한 많은 사람들의 고뇌가 담겨있는 것이죠. 훌륭한 프로그래머는 미래를 생각할 줄 압니다. 접근제어지시자와 같은 키워드의 문제뿐 아니라 여러분들이 앞으로 프로그램을 만들면서 명심해야할 메세지이죠. 앞으로 C++을 공부하다만나는 새로운 기능들은 대부분 편의성 뿐만 아니라 안전성때문에 만들어진 것들이 많습니다. 언어자체에서 안전한 코딩을 하기위한 기능을 제공하는 것이죠. 그만큼 안전한 코드를 만드는 것은 가장 중요하면서도 어려운 이슈입니다.

여러분 모두가 생각하는 프로그래머가 되기를 바라며 이 글을 마칩니다.

 

참고자료


https://youtube.com/playlist?list=PLXvgR_grOs1DFOWF65X0Zqnd_264x41u-

728x90
반응형

'C++' 카테고리의 다른 글

C++ 복사생성자 : 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)  (0) 2022.03.17
C++ 세번째, 생성자와 소멸자 그리고 this 포인터  (0) 2022.03.08
C++ 첫번째, 간단하게 보는 C와의 차이점  (0) 2022.02.12
    'C++' 카테고리의 다른 글
    • C++ 복사생성자 : 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)
    • C++ 세번째, 생성자와 소멸자 그리고 this 포인터
    • C++ 첫번째, 간단하게 보는 C와의 차이점
    Prototype Ghost
    Prototype Ghost

    티스토리툴바