본 글을 개인적 학습을 위한 글입니다. 틀린 내용이 있을 시 마구 지적해주시면 감사합니다.
이번 글에서는 C++에 추가된 핵심 요소인 객체와 클래스에 대해 알아보겠다.

세상 모든 것은 객체이다.
: 객체의 본질적인 특성
→ 객체를 캡슐로 싸서 그 내부를 보호하고 볼 수 없게 함.
목적 : 객체 내 데이터에 대한 보안, 보호, 외부 접근 제한
++ 하지만 객체의 일부 요소는 공개된다.
멤버 함수와 멤버 변수로 구성된다.
++ 객체는 상태(state)와 행동(behavior)으로 구성
ex) TV 객체 사례
상태 : on, off 속성 / 채널 / 음량
행동 : 켜기, 끄기, 채널 증가, 채널 감소, 음량 증가, 음량 감소
객체는 생성될 때 클래스의 모양을 그대로 가지고 있음
멤버 변수와 멤버 함수로 구성
메모리에 생성, 실체(Instance)라고도 부름
하나의 클래스 틀에서 찍어낸 여러 개의 객체 생성 가능
객체들은 상호 별도의 공간에 생성(like 변수)
클래스 선언과 구현으로 분리하는 이유는 클래스를 다른 파일에서 활용하기 위함이다.
구조체에 Accessibility를 갖게 되면 Class라 한다.
객체가 생성되는 시점에 자동으로 호출되는 멤버 함수
(클래스 이름과 동일한 멤버 함수)
기본 생성자(Default Constructor) - 매개 변수 없는 생성자
컴파일러에 의해 자동으로 생성
Q : 생성자는 꼭 있어야 하는가?
A : Yes. C++ 컴파일러는 객체가 생성될 때, 생성자를 반드시 호출한다.
Q : 개발자가 클래스에 생성자를 작성해 놓지 않는다면?
A : 컴파일러에 의해 기본 생성자가 자동으로 생성된다.
Q : 만일 하나라도 작성해 놓는다면?
A : 컴파일러는 기본 생성자(Default Constructor)를 생성하지 않는다.
객체가 소멸되는 시점에서 자동으로 호출되는 함수
오직 한 번만 자동 호출, 임의로 호출할 수 없음
객체 메모리 소멸 직전에 호출됨
목적
소멸자 함수의 이름은 클래스 이름 앞에 ‘~’를 붙인 형태이다.
소멸자도 리턴 타입이 없고, 어떤 값도 리턴하면 안 된다.
중복 불가능
소멸자가 선언되어 있지 않으면 기본 소멸자가 자동 생성
객체는 생성의 반대 순으로 소멸된다.
생성자, 소멸자는 반드시 public이어야 한다.
하지만 생성자는 private로 compile error가 발생하지 않는 한 가능하다.
왜 생성자를 private으로 선언하는 지에 대해서는 추후에 알아보겠다.
(대표적 예시 - 객체 복사 방지, 생성 방지 등)
캡슐화의 목적을 달성하기 위해 사용
→ 객체 보호, 보안
접근 지정 중복은 사용 가능하다.
class Example { public: void publicMethod1() { std::cout << "Public Method 1\n"; } private: int privateVar; // private 멤버 변수 public: void publicMethod2() { std::cout << "Public Method 2\n"; } protected: void protectedMethod() { std::cout << "Protected Method\n"; } private: void privateMethod() { std::cout << "Private Method\n"; } };
멤버 변수는 private 지정이 바람직하다.
이는 캡슐화의 주된 목적 때문에 특히 그렇다. 특정 값을 가져오기 위한 메소드를 Getter, Accessor이라 하고, 특정 값을 수정하기 위한 메소드를 Setter, Mutator라고 한다. 이러한 메소드를 잘 이용하여 캡슐화의 원칙이 무너지지 않게 끔하는 것이 좋은 C++ 코딩 습관이 될 것이다.
객체에 대한 포인터는 C언어의 포인터와 동일하다.
포인터 변수는 선언하고 반드시 주소를 부여하는 것이 좋다.
이는 C에서도 중요하게 여겨지는 원칙 중 하나이다. 포인터는 선언 후에 비어있는 채로 두지 않는 것이 좋다고 여겨진다. 이유라 함은 초기화되지 않은 포인터는 쓰레기 값을 가지게 되는데, 이를 예측하지 못하고 그대로 사용하게 되면 실행 중에 문제가 발생할 수 있다. 만일 최소한 NULL(nullptr)로 초기화하는 것은 포인터가 NULL이라면 디버깅 시 포인터가 초기화되지 않았음을 파악할 수 있다. 즉, 실행 중 문제가 발생해도 예측 가능하기 쉬워지기 때문이라고 할 수 있다.
Circle array[3] = { Circle(10), Circle(20), Circle() };
→ 2차원 배열도 동일한 원리로 동작한다.
참고 - Heap Memory
Heap은 운영체제가 소유하고 관리하는 메모리이다. 이는 모든 프로세스가 공유할 수 있는 메모리이기도 하다.
C++의 동적 메모리 할당 / 반환
delete 후
delete 사용 시 주의 사항
적절하지 않은 포인터로 delete를 하면 Runtime Error가 발생한다.
혹은 동일한 메모리를 두 번 반환하는 경우에도 오류가 발생한다.
이 또한 마찬가지로 new, delete 연산자를 사용한다.
동적 할당 메모리 초기화
int * p = new int(20);int ** p = new int[10](20); // Compile Error
int ** p = new int(20)[10]; // Compile Error할당 해제한 포인터를 NULL(nullptr)로 초기화한다면 더 좋은 코드를 짤 수 있다.
→ 선언 후, 초기화하는 것과 같은 맥락의 이야기
프로그램을 만들게 되면 사용하지 않는 메모리가 쌓이는 경우를 볼 수 있다. 프로그래밍 계에서 사용하지 않는 메모리라 함은 할당된 메모리이지만, 포인터로 가리킬 수 없는 메모리. 즉, 접근할 수 없는 메모리를 지칭한다. 자바와 같은 일부 언어는 이 사용하지 않는 메모리를 자체적으로 Collecting하여 정리를 해주지만, C++은 개발자가 이를 직접적으로 해제해주어야 한다. 이 때 이렇게 사용하지 않는(또는 할 수 없는) 메모리가 쌓이게 된다면, C와 C++에서는 메모리 누수(Memory Leak)이라 한다.


→ 프로그램이 종료되면, 운영체제는 누수 메모리를 모두 힙에 반환한다.
사실 main() 내에 할당된 메모리는 프로그램이 종료되면 자동으로 정리되므로 괜찮다. 하지만 main()이 아닌 특정 함수 내에서 동적 할당된 메모리를 정리하지 않는다면, 함수를 호출할 때마다 지속적으로 메모리가 쌓여서 프로그램의 메모리가 커지게 되며, 이는 문제를 일으킬 수 있다.
예를 들어, 재귀 함수가 대표적이다.
객체 자신의 포인터, 클래스의 멤버 함수 내에서만 사용 가능하다.
→ 개발자가 선언하는 변수가 아니고 컴파일러가 선언한 변수이다.
컴파일러에 의해 묵시적으로 삽입, 선언되는 매개 변수이다.
→ 각 객체 속의 this는 다른 객체의 this와는 철저히 구분된다.
Class Example{
int a;
void set(int a){
this->a = a;
}
}Class Example{
Example get(){
return this;
}
}클래스를 헤더 파일과 cpp 파일로 분리하여 작성한다.
→ 모두 클래스를 재사용하기 위한 목적이다.
class 파일을 분리하여 코드를 짜다보면 헤더 파일이 중복되는 문제가 발생하곤 한다. header를 include한다는 것은 컴파일 과정(중 전처리 과정)에서 그 위치에 그 파일의 내용을 붙여넣기한다는 것이다. 당연히 중복된 내용이 반복해서 include 된다면 문제가 발생할 것이다.
이를 조건 컴파일로 해결할 수 있다. → header에 설정
#ifdef EXAMPLE_H
#define EXAMPLE_H
...
#endif
오늘은 C++에서의 클래스에 대해 압축적으로 살펴 보았습니다. 다음 글에서는 클래스의 심화적인 내용들에 대해 훑어볼 수 있도록 하겠습니다.