Modern C++ 강의 정리 – Classes & Objects (1)

Classes

C++에서 등장한 클래스(Class)는 구조체(Structure)와 비교했을 때 유일한 차이점이 접근자의 기본값이 Private라는 점이다. 구조체의 경우 Public을 디폴트로 가진다.

생성자 (Constructor)

생성자는 클래스를 인스턴트화(instantiation)하는 시점에 호출되며, 클래스를 초기화하기 위한 목적으로 사용된다.

생성자는 리턴타입을 가지지 않으며, 오버로드(Overload)될 수 있다. 생성자의 종류로는 다음과 같은 것들이 있다.

  • 디폴트 생성자 (Default)
  • 매개 변수가 있는 생성자 (Parameterized)
  • 복사 생성자 (Copy)
  • 위임 생성자 (Delegated Constructor)
  • 상속생성자 (Inheriting Constructor)
디폴트 생성자 (Default)

어떤 인자도 없는 생성자다. 아래와 같이 사용자가 클래스를 인스턴트화 할 때 아무 인자도 넣어주지 않으면 호출된다.

Car c; // 기본 생성자 호출

사용자가 생성자를 따로 정의하지 않은 경우에는 컴파일러가 아무것도 하지 않는 기본 생성자를 만들어낸다. 단, 생성자가 하나라도 선언된 클래스는 컴파일러가 자동으로 기본 생성자를 만들지 않는다. 예를 들어 Car(int gas)와 같이 gas를 매개변수로 받는 생성자가 정의된 경우 컴파일러는 Car에 대한 기본 생성자를 만들지 않는다.

매개변수 생성자 (Parameterized Constructor)

하나 이상의 인자를 받는 생성자다. 컴파일러에 의해 자동으로 생성되지 않으며, 앞서 말했듯이 매개변수 생성자가 선언된 경우 컴파일러가 기본 생성자를 자동으로 만들지 않는다.

소멸자 (Destructor)

객체가 소멸될 때 호출되는 함수로 생성자에서 할당된 리소스를 반환하는 등의 작업을 위해 사용된다. 오버로드가 불가능하며 함수 인자를 가지지 않는다.

Non-static 멤버의 초기화

아래와 같이 클래스 멤버를 선언함과 동시에 초기화 시킬 수 있다.

class Class{
    int fuel{0};
    int x = 3;

이 방식을 사용하면 컴파일러가 자동으로 초기화 코드를 생성하며, 멤버들이 유효한 값으로 초기화 되는 것을 보장할 수 있다.

멤버 변수를 생성자에서도 초기화하는 경우에는 생성자의 초기화가 우선 순위를 가진다. 예를 들어 위 클래스의 생성자에서 x를 4로 초기화한다면 x는 4로 초기화된다.

모든 멤버 변수는 선언부에서 명시적인 타입을 가져야하며, auto 키워드를 통해 선언될 수 없다.


This 포인터

멤버 함수 내부에서 접근가능하며, 해당 함수를 호출한 객체를 가리킨다.

void Car::Accelerate() {
    this->speed++;
    (*this).fuel--;
}

다음과 같은 방식으로 사용가능하다. *접근자를 사용하면 this 포인터가 가리키는 객체의 레퍼런스를 얻을 수도 있다.


상수 멤버 함수 (Constant Member Functions)

상수 멤버 함수는 const 키워드를 가지며, 멤버 변수를 변경할 수 없는 함수다. 이 기능을 통해 Print와 같이 읽기 기능만 지원하는 함수를 생성할 수 있다. 아래와 같이 상수 멤버 함수에서 내부 멤버 변수의 변경을 시도하면 컴파일 에러가 발생한다.

int Car::getSpeed() const {
    this->fuel++; // 에러 발생
    return this->speed;
}

상수 객체(Constant object)는 초기화 이후에는 변경될 수 없기 때문에, 멤버 변수 변경 없는 상수 멤버 함수만 호출할 수 있다. 그러므로 멤버 함수가 멤버 변수를 수정하지 않는 경우에는 const로 선언되어야한다.

정적 멤버 (Static class member)

정적 멤버 변수와 함수는 한정자로 static 키워드를 가진다. 정적 멤버 변수는 객체가 아닌 클래스에 종속되어 있으므로, 객체가 하나도 없더라도 접근 가능하다. 정적 멤버 변수는 일반 멤버 변수들과 달리 클래스 내부에서 초기화 될 수 없으며, 반드시 클래스 밖에서 초기화 되어야 한다.

#include <iostream>

class Car {
    public:
        static int numCars;
        Car() {
            numCars++;
        }
        static void showNumCars() {
            std::cout << numCars;
        }
};
int Car::numCars = 0;

int main()
{
    Car c1, c2, c3;
    Car::showNumCars();
    return 0;
}

다음과 같이 Car 객체를 3개를 각각 정의하면, 생성자가 호출될 때마다 정적 멤버인 numCars를 1씩 증가시키는 것을 확인할 수 있다.

정적 멤버 함수의 경우에도 객체의 유무와 관계없이 실행이 가능하다. 정적 멤버 함수는 객체에 종속되지 않았기 때문에 this 포인터를 쓸 수 없으며 다른 정적 멤버 함수와 정적 멤버 변수에만 접근가능하다. 위의 예제와 같이 객체 없이도 클래스명::정적멤버함수명으로 즉시 콜이 가능하다.