[C++] 클래스(class)와 객체(object) 3편

HY K·2024년 8월 23일

명품 C++ 프로그래밍

목록 보기
14/24

계속해서 클래스와 객체에 대해서 생각해보자.
이번 포스팅에서 다룰 내용은 다음과 같다.
1. 소멸자(destructor)
2. 접근 지정자(access specifier)
3. 인라인 함수(inline function)
4. 구조체(struct)
5. C++ 프로그램 작성법


1. 소멸자(destructor)

소멸자는 C++ 객체가 소멸되는 시점에서 자동으로 호출되는 클래스의 멤버 함수이다. 소멸자가 가지고 있는 특징은 다음과 같다.

  1. 객체가 사라질 때 필요한 마무리 작업(메모리 반환, 파일 저장, 네트워크 해제 등)을 수행한다.
  2. 소멸자의 이름은 클래스 이름 앞에 ~ 를 붙힌다.
  3. 소멸자는 생성자와 같이 리턴 타입이 없고, 어떠한 값도 리턴해서는 안된다.
  4. 소멸자는 오직 1개만 존재하며 매개변수를 가지지 않는다.
  5. 소멸자가 선언되지 않으면 기본 소멸자(defautl destructor)가 자동으로 생성되어 작동한다.

예시 코드를 통해서 소멸자를 살펴보자.

#include<iostream>
using namespace std;

class Circle {
public:
	int radius;
	Circle();
	Circle(int r);
	~Circle(); // 소멸자
	double getArea();
};

Circle::Circle() {
	radius = 1;
	cout << radius << endl;
}

Circle::Circle(int r) {
	radius = r;
	cout << radius << endl;
}

Circle::~Circle() {
	cout << "종료합니다.." << endl;
}

double Circle::getArea() {
	return 3.14 * radius * radius;
}

int main() {
	Circle donut;
	Circle pizza(30);
	return 90;
}

객체는 생성의 반대 순으로 소멸하며, main() 함수가 종료되는 순간 main() 함수의 스택에 생성된 객체들이 소멸자에 의해서 소멸된다.

생성자와 소멸자의 실행 순서를 알기 위해서 먼저 다음 개념에 대해서 짚고 넘어가자.

💡 지역 객체(local object)
함수 내에 선언된 객체

💡 전역 객체(global object)
함수 바깥에 선언된 객체

지역 객체는 함수가 실행될 때 생성되고, 함수가 종료될 때 소멸되지만, 전역 객체는 프로그램이 로딩될 때 생성되고, main()이 종료된 뒤 프로그램 메모리가 사라질 때 소멸된다. 이 두 객체 모두 생성된 순서의 반대로 소멸된다.

예시 코드를 한번 들어보자.

#include<iostream>
using namespace std;

class Circle {
public:
	int radius;
	Circle();
	Circle(int r);
	~Circle(); // 소멸자
	double getArea();
};

Circle::Circle() {
	radius = 1;
	cout << radius << endl;
}

Circle::Circle(int r) {
	radius = r;
	cout << radius << endl;
}

Circle::~Circle() {
	cout << "반지름 "<<radius<<"인 객체를 종료합니다.." << endl;
}

double Circle::getArea() {
	return 3.14 * radius * radius;
}

Circle globalDonut(1000);
Circle globalPizza(2000);
// global object

void f() {
	Circle fDonut(100);
	Circle fPizza(200);
}

int main() {
	Circle maindonut;
	Circle mainPizza(30);
	f();
}

위 코드를 실행하면, 프로그램 순서가 다음과 같다는 것을 알 수 있다.

  1. 프로그램 로딩 : global 객체들 생성
  2. main() 함수 시작 : mainDonut, mainPizza 생성
  3. f() 함수 시작 : fDonut, fPizza 생성
  4. f() 함수 종료 : f() 함수 내부의 지역 객체 소멸
  5. main() 함수 종료 : main() 함수 내부의 지역 객체 소멸
  6. 프로그램 종료 : global 객체들 소멸

2. 접근 지정자

앞서 살펴본 접근 지정자에 대해서 더 자세하게 알아보자.
접근 지정자는 객체를 캡슐화 하여, 외부로부터의 접근을 제한하고 객체를 보호하기 위해서 사용하는 기능이다.

C++에서는 다음과 같이 3가지 접근 지정자를 사용한다.

  • public : 클래스 내부와 외부 상관없이 프로그램의 모든 함수들에 의해서 접근이 가능하다.
  • private : 클래스 내의 멤버 함수들에 의해서만 접근이 가능하다.
  • protected : 클래스 내의 멤버 함수와, 이 클래스를 상속받은 자식 클래스의 멤버 함수에게만 접근이 허용되도록 한다.

멤버 변수와 멤버 함수에 대한 접근 지정은 클래스 선언부에서 접근 지정자 다음에 콜론(:)을 찍고 선언하는 식으로 이루어진다. 또한, 접근 지정자는 여러번 사용될 수 있고, 접근 지정자가 선언되면 다른 접근 지정자가 선언될 때까지 모든 멤버에게 적용된다.

접근 지정을 하지 않을 수도 있는데, 그럴 경우 디폴트 값으로 정해진 것은 private이다. 객체는 기본적으로 캡슐화를 원칙으로 하기 때문이다. 따라서 클래스를 작성할 때 최소한 멤버 변수들은 외부에서 접근하지 못하도록 private로 선언하는 것이 보다 적절한 작성 방법이다.

반면, 클래스 외부에서 객체를 생성하기 위해서라면, 생성자는 반드시 public으로 선언해야 한다. 만약 public으로 선언되지 않으면 컴파일 오류가 발생한다!! 외부에서 클래스를 생성하지 않고, 내부적으로만 접근해 생성하도록 하거나, 상속받은 클래스만이 생성할 수 있도록 조치한다면 private나 protected로 선언해도 무방하다.


3. 인라인 함수

함수는 굉장히 유용하고 편리한 코드 블록이나, 호출과 실행 사이에 시간 소요가 발생한다. 함수 호출과 실행 사이의 단계는 다음과 같다.

  1. 돌아올 리턴 주소 저장
  2. CPU 레지스터 저장
  3. 함수의 매개변수를 스택에 저장
  4. 함수 실행
  5. 함수의 리턴 값을 임시 저장소에 저장
  6. 저장한 레지스터 값을 CPU에 복귀
  7. 돌아갈 주소를 알아내어 리턴

따라서 함수 호출에 따른 오버헤드(overhead) 시간이 필히 발생하게 된다. 특히, 짧은 코드를 함수로 만들어서 함수의 개수가 늘어날수록 함수 호출과 실행에 따른 오버헤드가 폭발적으로 증가하게 된다.

인라인 함수(inline function) 는 짧은 코드로 구성된 함수에 대해, 함수 호출 오버헤드로 인한 프로그램 실행 속도 저하를 막기 위해서 도입된 기능이다. 인라인 함수는 다음과 같이, 함수 리턴 타입 앞에 inline 키워드를 이용하여 선언한다.

inline int odd(int x);

컴파일러에서는 인라인 함수를 호출하는 곳에, 해당 함수의 코드를 그대로 삽입하여 함수 호출이 일어나지 않도록 한다. 이를 통해 오버헤드가 발생하지 않도록 제어할 수 있다.

이러한 인라인 함수는, 상대적으로 코드 길이가 짧고 크기가 작은 함수들, 예를 들면 멤버 변수들의 값을 읽고 쓰는 함수들에게 적용하는 것이 매우 유용하다. 다만, 모든 프로그래밍 기법이 그러하듯 함부로 남발하면 오히려 손해를 볼 수 있으니 적절히 조정하는 것이 필요하다.

그리고 컴파일러에 따라서, 인라인 키워드를 무시할 수 있다. 강제적인 명령이 아니기 때문이다. 통상적으로 재귀 함수(recursion), static 변수, 반복문, switch 문, goto 문 등은 인라인 함수 요청ㅇ을 무시한다.

C++의 클래스에 대해서는, 생성자를 포함해서 클래스의 모든 멤버 함수를 인라인으로 선언할 수 있다. 하지만 또 다른 좋은 방법도 있는데, 함수의 크기가 그렇게 크지 않다면 클래스의 선언부에 직접 구현을 해도 인라인 선언과 동일한 효과를 가진다. 예시를 통해 살펴보자. 다음 두 코드는 동일한 효과를 가진다.

class Circle{
private:
	int radius;
public:
	Circle();
};

inline Circle::Circle(){ radius = 1; }
class Circle{
private:
	int radius;
public:
	Circle(){
    	radius = 1;
    }
};]

4. 구조체(struct)

이번에는 C언어와의 호환성을 위해서 도입된 C++의 구조체를 알아보도록 하자. C++의 구조체는 C언어의 구조체의 기능을 확장하여, 클래스와 동일한 구조와 기능을 가지게 된다.

struct 키워드를 통해서 선언하며, 멤버 변수와 멤버 함수를 가지고, 접근 지정자도 작성해줘야 한다.

struct structName{
private:
public:
protected:
};

객체 선언 및 생성 역시 클래스와 마찬가지로 하면 된다.
다만, 클래스와 한가지 다른 점이 있다면, 클래스의 디폴트 접근 지정자는 private인 반면, 구조체의 디폴트 접근 지정자는 public이다.

profile
로봇, 드론, SLAM, 제어 공학 초보

0개의 댓글