C++ | 객체란 무엇인가 객체 느낌 알기

C++ | 객체란 무엇인가 객체 느낌 알기

‘객체지향언어’, ‘OOP’, 객체라는 개념이 참 와닿지 않고 간단한 구분조차 힘들었습니다. 수업으로 들었던 C 언어 기초도 여기저기 구멍이 뚫린 채로 개념을 기억하고 있는데, 클래스니 메서드니 아무리 비유를 들어도 또렷하게 연상되지 않았던 것 같습니다.

오렌지, 빵틀, 붕어빵틀, 그러한 비유로 용어를 설명을 하는데 그것이 코드로 와서 그게 무슨 말인지 비유가 전혀 와닿지 않았습니다. 그래서 이제서야 나름의 경험을 토대로 C++을 써오면서 느낀 객체를 이야기해보려고 합니다.

개발과 연이 없던 친구가 문득 게임 개발 이야기를 하면서 ‘그런데 객체가 뭐야?’라는 질문에 문득 말문이 턱 막혔던 기억이 납니다.

뭔가 이리저리 써왔기에 ‘이렇게 쓰긴 했는데…’ 정도의 느낌과 경험의 시간이 있으나 상대방도 받아들일 수 있게끔 간단하게 설명하려고 보니 도저히 단순화하지 못했습니다. ‘나는 아직도 객체란 개념을 제대로 이해하고 있지 않구나’ 스스로 반성하는 시간이었죠.

C 언어라는 저수준(Low Level)을 제외하고 대부분 활용되는 “클래스(Class)”와 “객체”를 코드와 함께 제가 나름대로 이해하고 있는 객체를 다뤄보도록 하겠습니다.

객체는 OOOO이다.

객체지향언어(OOP)를 처음 접하고 어떻게 이를 사용하는지 감이 안 잡히는 분이라면, 주입식으로 이렇게 외워보는 것도 좋을 것 같습니다.

객체(Object)는 ‘변수 이름’이다. + 클래스(Class)는 ‘변수 타입’이다.

그리고 이제 코드를 작성할 때 ‘클래스(Class)’는 ‘변수 타입’처럼 쓴다고 외워봅시다. 그럼 여기서 한 번 비교를 해봅시다. 정수 타입의 변수를 선언할 때 어떤 식으로 했던가요?

int value;

위와 같이 선언했을 겁니다. 우선 데이터 타입의 크기 같은 내용은 제쳐 두고 정수 타입으로 대표적으로 사용하는 int를 사용하여 선언해보았습니다. 그리고 시리얼 통신 중 Uart를 사용하도록 구성한 클래스의 이름으로 ‘SerialUart’라고 지었다고 가정해보죠. 이를 간단하게 표현하면, 아래와 같이 표현됩니다.

class SerialUart
{
    public:
        void connectUart();
        void disconnectUart();
};

그럼 이 클래스의 객체를 선언하려면 어떻게 할까요?

SerialUart serial_uart;
SerialUart serial;
SerialUart uart;

위와 같이 클래스의 객체를 다양한 이름으로 선언할 수 있습니다. 객체는 <’변수이름’이다>는 느낌을 강조한 부분을 다시 기억하시면서, 그럼 정수 선언과 클래스의 객체 선언을 같이 놓고 보죠.

int value;

SerialUart serial_uart;

객체를 선언하고 포인트(.)를 찍어보면 클래스 안에 있는 함수를 쓸 것인지 결과를 표시해 줍니다. 포인트(.)를 통해서 클래스 안에 작성한 함수에 접근할 수 있죠.

그림1

객체를 포인터 형식으로 선언했다면, ‘->’를 사용해서 함수에 접근할 수 있습니다.

그림2

여기서 용어에 대한 개념을 확장하면, connectUart()와 disconnectUart() 함수가 SerialUart 클래스 안에 선언해두었지요. 이렇게 SerialUart 클래스 안에 있는 함수를 ‘메서드(Method)’라고 칭합니다.

클래스 안에는 여러 함수와 변수가 있을 수 있습니다. 여러분이 정한 클래스 이름을 ‘변수 타입’처럼 사용하고 이 클래스에 대해 여러분이 원하는 ‘변수 이름’처럼 작성한 것을 ‘객체(Object)’라고 할 수 있습니다.

이 객체가 클래스를 타입으로 하여 선언이 이루어지는 순간, 클래스 안에 있는 변수나 함수에 접근할 수 있습니다. Object라는 영어 단어가 ‘대상’이라는 의미를 가지는 것처럼 클래스 안에 있는 무언가에 접근할 수 있게 해주는 매개체이자, 대상이 되어주는 존재이죠.

그림3

PointCloud 클래스를 추가하고 PointCloud로 타입을 정하고 객체를 serial_uart라고 이름을 그대로 두었습니다. 이 객체에 포인트를 찍어 보면 보시다시피 PointCloud 안에 있는 함수나 변수로 접근합니다.

다만, 객체 이름을 serial_uart라고 했으니 다른 사람이 읽으면 혼란을 줄 수 밖에 없겠죠. 그렇기 때문에 클래스 이름을 지을 때 좀 더 고민해서 키워드를 정하고, 객체도 여러 개를 사용한다면 이를 구분할 수 있도록 정합니다.

여기서는 main() 안에 객체를 선언해두었으니 객체가 어떤 클래스를 지칭하는지 알기 쉬우나, 이게 헤더파일에 따로 선언해두었으면 어떨까요?​

헤더파일에 직접 들어가서 보지 않는 이상, serial_uart라는 객체 이름이 어떤 클래스를 지칭하고 있는지 알 수 없습니다. 단지 serial_uart라는 이름만 봤을 때, 시리얼 통신에서 Uart 통신과 관련한거겠구나 싶었는데, 헤더파일로 가보니 대뜸 PointCloud 클래스로 선언되어 있다면? 개발자를 괴롭히고 싶은건가 싶겠죠?

이런 식으로 캡슐로 싸서 그 내부를 보호하고 볼 수 없게 한다는 의미로 ‘캡슐화’라는 용어를 객체의 특성이라고 이야기하는데 이러한 원리로 인해서 붙여진 것입니다. 다시 정리해봅시다.

  • 1. 클래스는 ‘변수 타입’처럼
  • 2. 객체는 ‘변수 이름’처럼 작성한다.
  • 3. 객체를 통해 함수나 변수에 접근하여 사용한다.

이 접근에 대해서 조금 더 자세히 살펴봅시다.

접근지정자와 멤버

클래스 안에는 다양한 변수와 다양한 함수들을 구성할 수 있습니다. 이들을 묶어서 ‘멤버(Member)’라고 부를 수 있습니다. 위의 예시 코드를 다시 가져와보죠.

그림4

SerialUart 안에 있는 함수 connectUart()와 disconnectUart()는 SerialUart 클래스의 메서드라고 칭할 수도 있고, 멤버 함수(Member Fuction)라고 칭할 수 있습니다. 그럼 정수로 선언한 packet 변수는 무엇이라고 할까요?

‘멤버 변수(Member Variable)’ 또는 ‘멤버 데이터(Data Member)’라고 부릅니다.

C++ 클래스에서는 이 멤버들을 접근할 수 있는 정도를 정할 수 있습니다. 이때 사용하는 것을 ‘접근 지정자(Access Specifier)’ 또는 ‘접근 제한자(Access Modifier)’라고 합니다.

위에 예시 코드에서는 함수도, 변수도 ‘public:’ 아래에 선언해두었습니다. 이때, ‘public:’이 바로 접근 지정자입니다.

  • public
  • private
  • protected

이렇게 3가지 있는데 protected는 ‘상속’이라는 C++의 개념과 같이 설명이 필요하니, 이 글에서는 public과 private만 살펴보도록 하겠습니다.

그림5

connectUart()함수와 정수 타입의 packet 변수는 private에 두고, 나머지 disconnectUart() 함수는 public에 두었습니다. 그랬더니 serial_uart 객체에 포인트를 찍어 접근 가능한 멤버를 살펴보면 public에 선언한 disconnectUart() 함수만 결과가 표시되고 나머지는 사용할 수 없어 따로 표시되지 않습니다.

이렇게 클래스 외부에서도 사용하려면 public에 나열하고 클래스 내부에서만 사용하려면 private에 두는 것을 구분하는게 좋습니다.

private에 작성한 함수는 클래스 내부에서 여러 동작을 하도록 두고, 변수 또한 클래스 내부에서 연산이나 복사가 이루어지게 합니다. 멤버 전체를 private으로 사용한다면 다른 곳에서 데이터가 변질될 우려가 없겠죠. 외부에서 접근을 제한하니까요.

다시 정리해보죠.

  • 1. 클래스 내부의 변수 또는 함수들을 ‘멤버’라고 한다.
  • 2. 클래스 접근 제한을 설정하는 ‘접근지정자’ 또는 ‘접근제한자’가 있다.
  • 3. 접근지정자 public 아래의 변수나 함수는 클래스 외부에서도 접근 가능하고,
  • 4. 접근지정자 private 아래의 변수나 함수는 클래스 내부에서만 접근 가능하다.

이 정도의 개념이면, 객체란 무엇일까라는 질문에 용어 정의를 살펴보는 것보다는 조금 객체를 이해하는데 도움이 되지 않을까 싶습니다. 부족하지만 객체와 클래스를 받아들이는데 도움이 되었으면 좋겠습니다.


코드를 작성하면 언제나 ‘다른 사람이 읽어도 이해하기 쉽게 코드를 작성한다’는 마음가짐으로 임해야 합니다. 코드라는 것 자체가 이미 어려운데 이걸 읽고 이해하기 쉽다?는 의미가 초보자에겐 어이없을지도 모르겠습니다.

그런 의미에서 클래스 이름을 정할 때 어떤 범주에서 어떤 기능에서 키워드가 직관적으로 받아들여지는지를 잘 판단해서 정하는게 좋습니다.

코딩테스트만 접해오셨다면, 변수나 함수 이름이 엄청 간결하다 못해 짧게 표현하는게 익숙하실 수도 있습니다. ‘number’라면 ‘n’ 이런 식으로 표현하는 것 말이죠.

그런데 실무에서 막상 변수나 함수 이름을 정할 때, 짧은 것보다는 길고 자세한게 더 좋다는 것을 처음 접했을 때, 신선한 충격이었습니다.

함수가 만약 정수 배열을 실수 배열로 변환하고 이를 마지막에 초기화한다면?

  • ​convertInteger_Array_to_Float_Array_and_Reset_Total_Array_Data()

말도 안 되는 것처럼 보일 수 있지만, 본인이 작성하고 있는 코드를 계속해서 들여다봐야 하는 사람이 본인이라면, 여러 번 읽어서 의미를 다시금 이해해야 하는 과정보다는 저렇게 무식하게 길어보이더라도 저 함수가 무엇을 하는지 이해할 수 있는게 훨씬 좋은 경우가 많습니다. ‘아, 저 함수 들어가면 변환되고 초기화까지 거치는구나’를 함수 이름으로 알 수 있으니까요.

의외로 시간에 쫓겨 코드를 작업하다 보면, 다시금 코드를 읽을 때 이게 무엇을 하는 역할인지 기억이 날아가버리는 경우가 많습니다.

주석이라도 덕지덕지 붙여두면 다행이지만 없다면, 결국은 그 수고로움을 매번 반복해야 하니까 함수나 변수의 이름을 정할 때도 늘 이해하기 쉽게 구성하려고 노력하는게 얼마나 중요한지 항상 경험하는 듯 합니다.

코드는 간결한게 좋지 않을까 싶었지만 현장에서는 오히려 길고 자세한 함수 이름이 더 가독성이 있고, 코드 줄을 줄이기 위해 요약하는 것보다는 한 번 읽으면 흐름이 이해하게 코드를 늘려서 자세히 작성하는 경우가 빈번했습니다.

코드도 결국 글쓰기가 되어가는게 참 재미있는 일인듯 합니다.