2025. 4. 29. 18:05ㆍProgramming Language/C++
C++에서 클래스에 등장하는 const는 여러 위치에 따라 의미가 달라지기에 5개의 주요 형태에 대해서 확인해보자.
1. 멤버 변수 선언 시의 const
class Car {
private:
const int id; // 수정 불가한 멤버 변수
};
이렇게 멤버 변수를 선언할때 const를 선언해줄 수 있는데 이렇게 선언하면 객체가 생성되고 나서부터 해당 멤버 변수는 변경이 불가능한 상태로 생성이 된다.
이를 초기화하는 방식법은
Car(int i) : id(i) {} // ✅ OK
Car() { id = 5; } // ❌ 에러
위와 같이 생성자 이니셜라이저 리스트에서만 가능하고 대입하는 식의 방법은 불가능하다.
또한 그냥 멤버 함수 자체의 정의와 동시에 초기화도 가능하다.
class Car {
private:
const int id = 10;
};
이 경우에도 생성자 이니셜라이저 리스트를 사용해서 초기화 값을 변경하는 것도 가능하다.
2. 멤버 함수 뒤의 const
멤버 함수 뒤에 const를 사용하게 되면
class Car {
private:
int speed;
public:
int getSpeed() const {
return speed;
}
};
이 함수가 멤버 변수(객체 상태)를 수정하지 않겠다고 선언하는 것으로 사실 기존에 this를 받아오는 부분이
int getSpeed() {
return speed;
}
↓
int getSpeed(Car* const this) {
return speed;
}
와 같이 매개변수로 본인 객체 타입의 this를 함수로 받아 사용하게 되는 거랑 같은데 여기서 함수 뒤에 const를 붙이면
int getSpeed() const {
return speed;
}
↓
int getSpeed(const Car* const this) {
return speed;
}
이렇게 되는 것이랑 동일하다
그러면 this를 사용해서 멤버 변수를 변경하는 것만 불가능하냐?
그렇지 않다.
사실 우리가
class Car {
private:
int speed;
public:
int getSpeed() {
speed = 10;
return speed;
}
};
과 같이 선언하면 함수의 내부에 있는 대입 부분이
speed = 10;
// ↓ 내부적으로는
(*this).speed = 10;
으로 해석 되기 때문에 함수 내부에서 내부 멤버 변수에 접근하면 무조건 this를 사용한것과 동일하다고 봐야한다.
그럼 함수에 const를 붙이는 이유는 뭘까?
이는 사용자가 원하는 바에 따라서 해당 함수가 멤버 변수의 값을 변경하지 못하는 함수로 정의되기 바라는 경우(읽기 전용 함수)나
class Car {
int speed;
public:
int getSpeed() const { return speed; } // ✅ 읽기만 하겠다고 보증
};
const 객체에서도 호출 가능하게 하기 위해서이다.
const 객체에서도 호출 가능하게 하기 위해서에 대해서 알기 위해서는 우선 먼저 알아야하는 부분들이 존재하기에 해당 내용은 아래 접힌글에 작성해두겠다.
먼저 이를 알기 위해서는 클래스 포인터에 대해서 알아야한다.
클래스 포인터
클래스 포인터는 간단히 말해 클래스 타입 객체를 가리키는 포인터로 int*가 int를 가리키듯, Car*는 Car 객체를 가리키는 형태가 된다.
클래스 포인터의 기본 선언
class Car {
public:
int speed;
void drive() {
std::cout << "Driving at " << speed << " km/h\n";
}
};
위와 같이 클래스가 정의되어 있다고 할때 클래스 포인터의 기본적인 선언을 하기 위해서는 우선 해당 클래스의 객체가 필요하다
Car c1;
Car* ptr = &c1;
or
Car* ptr = new Car(); // 동적할당 할 경우 해제가 반드시 필요 (delete ptr;)
과 같이 클래스 객체를 만들어 준 다음에 그걸 포인터에 집어 넣을 수 있다.
이렇게 만들어진 클래스 포인터의 경우는 멤버 변수 혹은 함수에 접근하기 위해서는 -> 연산자 혹은 *으로 감싼 후 . 연산자를 사용해줘야 한다.
ptr->speed = 80; // (*ptr).speed = 80; 과 동일
ptr->drive(); // (*ptr).drive(); 와 동일
const 클래스 포인터
그리고 우리가 보려고 했던 부분인 const 클래스 포인터에 대해서 확인해보면 우선 const 클래스 포인터를 그냥 클래스 포인터에 할당하는 것은 불가능하다.
const Car* car = new Car();
Car* car1 = car; // 불가능
위와 같이 const 클래스 포인터인 car을 변경 가능한 Car 클래스 포인터로 받으면 내부에 멤버 변수를 변경하는 것이 가능하도록 설계가 되는 것이기에 에초에 const를 선언한 의미가 깨지게 된다.
이를 컴파일러가 허락 해주지는 않는다.
이건 기본 타입에서
const int a = 10;
int* p = &a; // 불가능
와 같은 상황이랑 동일한 것이다.
그러나 이 반대는 상관 없다.
Car* car = new Car();
const Car* car1 = car; // 가능
이는 수정 가능한 클래스 포인터를 바라보고 있는 클래스 포인터를 const, 읽기 전용으로 만들겠다는 것으로 이건 car1으로만 car 을 안바꾸길 바라는 형태로 선언되는 것이라 문제가 없다.
하나 예시를 들어보자면
class Car {
private:
int speed;
public:
int getSpeed(){
return speed;
}
};
int main(){
const Car car;
car.getSpeed();
}
와 같이 getSpeed함수는 const 없이 정의되어 있는데 Car 객체는 const로 전달한다면 car.getSpeed를 불렀을때
const Car car;
car.getSpeed();
↓
car.getSpeed(&car)
을 전달하는 것과 같고
그러면 getSpeed에서는 이걸
int getSpeed(Car* this){
return speed;
}
으로 매개변수를 받으면서 최종적으로는
const Car car;
↓ (getSpeed 함수에 전달인자를 전달하는 과정에 매개변수에 전달인자를 넣을때)
Car* const this = &car;
↓ &car를 본 타입으로 변경해보면
Car* const this = const Car* car;
이 되는 것이다.
이건 const 타입을 non-const 타입에 넣는 방식으로 이렇게 되면 this를 사용해서 const Car* car 의 멤버 변수를 변경할 수 있는 형태로 구현되는 것이다.
그렇기 때문에 이를 컴파일러에서 허용해주지 않는다.
그렇기에 이런 경우에는 함수의 뒤에 const를 넣어주면
int getSpeed(const Car* const this) {
return speed;
}
가 되어서
const Car car;
↓ (getSpeed const 함수에 전달인자를 전달하는 과정에 매개변수에 전달인자를 넣을때)
const Car* const this = &car;
↓ &car를 본 타입으로 변경해보면
const Car* const this = const Car* car;
로 타입에 맞게 값이 들어갈 수 있게 된다.
'Programming Language > C++' 카테고리의 다른 글
Part2::Ch 01. 클래스 - 09. 멤버 함수 포인터 (0) | 2025.04.29 |
---|---|
Part2::Ch 01. 클래스 - 08. 정적 멤버 (0) | 2025.04.29 |
Part2::Ch 01. 클래스 - 06. this 포인터 (0) | 2025.04.29 |
Part2::Ch 01. 클래스 - 05. 선언과 정의의 분리 & 전방 선언 (0) | 2025.04.29 |
Part2::Ch 01. 클래스 - 04. 파괴자(Destructor) (0) | 2025.04.29 |