const 키워드

2024. 12. 31. 12:57·C++

블로그 글 TIL day8 에서도 한 번 언급했었는데, 중요하다고 생각이 들어 다시 정리해보려고 한다.

effective c++의 항목 3을 많이 참고하여 정리했다.

  • 리마인드
    • const 가 함수 앞에 있다면, 반환값을 상수화 시키는 것
    • const 가 함수 뒤에 있다면, 멤버 변수의 수정을 막는 것

1. 포인터변수의 const

  • 순서대로
    • 비상수 포인터, 비상수 데이터
    • 비상수 포인터, 상수 데이터
    • 상수 포인터, 비상수 데이터
    • 상수 포인터, 상수 데이터
#include <iostream>
using namespace std;

int main()
{
	char greeting[] = "Hello\n";

	char* p = greeting;
	const char* p = greeting;
	char* const p = greeting;
	const char* const p = greeting;
}
  • 왜?
    • const 키워드가 * 왼쪽에 있다면 포인터가 가리키는 대상은 상수 (char 가 const) 
      • const char* greeting; 과
      • char const* greeting; 은 같은 의미이다.
    • const 키워드가 * 오른쪽에 있다면 포인터 자체가 상수 (p 가 const)
      • char* const greeting;
  • 아래 코드가 const의 위치에 따라 불가능한 작업을 보여준다. (주석 부분이 불가능한 작업)
    • 상수 포인터는 포인터가 가리키는 위치 바꾸지 못하며,
    • 상수 데이터는 포인터가 가리키는 위치의 값을 바꾸지 못한다.
#include <iostream>
using namespace std;

int main()
{
	int value1[] = { 1,2,3 };
	int value2[] = { 10,20,30 };

	int* p1 = value1;
	p1 = value2;
	*p1 = 100;

	const int* p2 = value1;
	p2 = value2;
	//*p2 = 100;

	int* const p3 = value1;
	//p3 = value2;
	*p3 = 100;

	const int* const p4 = value1;
	//p4 = value2;
	//*p4 = 100;	
}

2. STL iterator

  • STL의 반복자는 포인터를 본떠 만든 것이기 때문에 포인터의 동작과 굉장히 유사하다.
  • 아래의 예시를 보면 이해할 수 있다.
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> vec = { 10, 20, 30 };

	const vector<int>::iterator it1 = vec.begin(); //상수 포인터
	*it1 = 100;
	//++it1;

	vector<int>::const_iterator it2 = vec.begin(); //상수 데이터
	//*it2 = 100;
	++it2;
}

3. 함수에서의 const

  • const를 제대로 붙이지 않는다면, 아래와 같은 상황이 생길 수 있다.
    • * 연산자의 반환값이 상수 객체가 아니라면
    • a * b에 대입이 가능해진다.
class Rational { ... };

Rational operator*(const Rational& lhs, const Rational& rhs); //반환값을 상수로 만들지 않음

...

Rational a;
Rational b;
Rational c;

(a * b) = c; //에러가 발생하지 않음

4. 매개변수에서의 const

  • const 타입의 지역객체와 똑같이 동작한다.
  • 가능한 많이 붙이자 -> 수정하지 못하게 할 것이라면 꼭 const를 붙이자

5. 상수 멤버 함수

  • 상수 멤버 함수: 멤버 함수 뒤에 const가 붙은 함수
  • 멤버함수에 붙는 const 키워드의 역할은 "해당 멤버 함수가 상수 객체에 대해 호출될 함수이다" 라는 것을 알려준다.
  • 중요한 이유
    1. 클래스의 인터페이스를 이해하기 좋게 하려고
      • 클래스로 만들어진 객체를 변경할 수 있는 함수와 변경할 수 없는 함수가 무엇인지 사용자가 알 수 있다.
    2. 상수 객체를 사용할 수 있게 하려고
  • const 키워드가 있고 없고의 차이만 있는 멤버 함수들은 오버로딩이 가능하다.
const char& operator[] (std::size_t position) const //상수 객체에 대한 []
{
	return text[position];
}

char& operator[] (std::size_t position) //비 상수 객체에 대한 []
{
	return text[position];
}
  • 위의 함수를 통해 아래 코드를 실행하면, 다음과 같은 결과를 얻을 수 있다.
    • 마지막 줄 코드가 에러가 발생하는 이유는 상수 데이터를 수정하려고 했기 때문이다. 
TextBlock tb("Hello");
const TextBlock ctb("World");

std::cout << tb[0]; //상수버전
tb[0] = 'x'; //비상수 버전
std::cout << ctb[0]; //상수버전
ctb[0] = 'x'; //에러!
  • 추가) 왜 위의 오버로딩 된 함수에서 char& 타입으로 반환을 했을까?
    • char이 반환타입이라면, 복사된 값이 return 된다.
    • 위의 코드에서본 예시처럼 tb[0] = 'x'; 이런식으로 수정할 일이 있을때, 원본을 수정해야지 사본을 수정하는 건 우리가 원하는 동작이 아니기 때문이다.
    • 그래서 char& 로 반환하지 않으면, tb[0] = 'x'; 코드에서 컴파일 에러가 발생한다.

멤버함수가 상수 멤버라는 것의 의미 

  • 비트수준 상수성 (물리적 상수성)
    • 어떤 멤버함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 그 멤버 함수가 const임을 인정하는 개념
    • 그러나 이를 위반함(상수 객체가 상수 멤버함수를 호출했는데, 값이 변함) 에도 오류가 발생하지 않을 수 있는 경우가 존재한다. (아래코드 참고)
class CTextBlock
{
public:
	...
    char& operator[](std::size_t position) const
    {
    	return pText[position];
    }
    ...
private:
	char* pText;
};

...

const CTextBlock cctb("Hello");
char* pc = &cctb[0];
*pc = 'J'; // cctb가 "Jello" 가 된다!

 

 

  • 논리적 상수성
    • 비트수준 상수성을 해결하기 위해 나온 개념 (결과적으로 이 방식을 통해 코드를 짜야한다.)
    • 상수 멤버라고 해서 객체의 한 비트도 수정하지 못하게 하는 것이 아니라, 몇 비트정도는 바꿀 수 있지만, 사용자 측에서만 알아채지 못하게 하자는 개념
    • 아래 코드의 개념처럼 유효성 체크를 통해 CTextBlock의 상수 객체에 대해서는 문제가 발생하지 않도록 하는 개념이다.
    • 그러나, length 함수를 구현함에 있어서 문제가 발생한다. (상수 멤버함수에서 값 수정함)
class CTextBlock
{
public:
    std::size_t length() const;
    
private:
    char *pText;
    std::size_t textlength; //바로 직전에 계산한 텍스트 길이
    bool lengthIsValid;
};

std::size_t CTextBlock::length() const
{
    if(!lengthIsValid)
    {
    	textlength = std::strlen(pText); //그러나 에러 발생! const 함수인데 값 수정함
        lengthIsValid = true;
    }
    return textlength;
}
  • 해결책
    • mutable 키워드를 사용하는 것
    • mutable은 상수성이 있는 곳에서 멤버변수의 수정을 허락해주는 키워드이다.
class CTextBlock
{
public:
    std::size_t length() const;
    
private:
    char *pText;
    mutable std::size_t textlength; 
    mutable bool lengthIsValid;
};

std::size_t CTextBlock::length() const
{
    if(!lengthIsValid)
    {
    	textlength = std::strlen(pText); //제대로 동작한다
        lengthIsValid = true;
    }
    return textlength;
}

6. 상수 멤버 및 비상수 멤버 함수에서 코드 중복을 피하기

  • 위의 결과로 상수성을 지킬 수 있게 해줬지만, 코드의 중복이라는 문제가 남아있다.
  • 우리가 [] 연산자를 오버로딩 했던 것을 보면, 같은 동작을 하면서 const 여부만 다른 함수가 존재하게 되고 코드의 중복이 발생하게 된다.
const char& operator[](std::size_t position) const
{
	//경계검사
   	//접근 데이터 로깅
    	//자료 무결성 검증
   
	return text[position];
}

const char& operator[](std::size_t position) const
{
	//경계검사
   	//접근 데이터 로깅
    	//자료 무결성 검증
    
	return text[position];
}
  • 이 문제를 해결하기 위해서는 비상수 버전에 상수버전을 call하도록 캐스팅을 해서 구현하는 방법이다.
  • 캐스팅은 최대한 안쓰는 것이 좋지만, 코드의 중복을 줄이기 위해서는 사용하는건 어쩔 수 없다.
    • const_cast를 통해 상수 operator [] 의 반환 값에서 const를 떼어내기
    • static_cast를 통해 *this타입에 const를 붙이기
class TextBlock
{
public:
    const char& operator[] (std::size_t position) const
    {
    	...
        
        return text[position];
    }
    
    char& operator[] (std::size_t position) 
    {
    	return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
    }    
}

7. 결론

  • const를 붙일 수 있는 곳에 최대한 붙여서 컴파일러가 에러를 잡아주도록 하자
  • 컴파일러 딴에서는 비트수준 상수성을 지켜야 하지만, 코드를 짤때는 논리적인 상수성으로 코드를 짜자
  • 상수 멤버함수와 비상수 멤버 함수가 기능적으로 똑같다면, 코드의 중복을 피하기 위해 비상수 멤버함수에 캐스팅을 통해 상수 멤버함수를 호출하자

'C++' 카테고리의 다른 글

C++ TIL day 12  (1) 2025.01.02
C++ TIL day 11 (포인터 연산 문제)  (2) 2024.12.31
C++ TIL day 10  (0) 2024.12.30
C++ 템플릿 - 헤더파일에서 구현하자  (0) 2024.12.30
C++ 코딩 스탠다드  (2) 2024.12.30
'C++' 카테고리의 다른 글
  • C++ TIL day 12
  • C++ TIL day 11 (포인터 연산 문제)
  • C++ TIL day 10
  • C++ 템플릿 - 헤더파일에서 구현하자
gbleem
gbleem
gbleem 님의 블로그 입니다.
  • gbleem
    gbleem 님의 블로그
    gbleem
  • 전체
    오늘
    어제
    • 분류 전체보기 (184)
      • Unreal Engine (73)
      • C++ (19)
      • 알고리즘(코딩테스트) (27)
      • TIL (60)
      • CS (4)
      • 툴 (1)
  • 블로그 메뉴

    • 홈
    • 카테고리
  • 링크

    • 과제용 깃허브
    • 깃허브
    • velog
  • 공지사항

  • 인기 글

  • 태그

    BFS
    cin함수
    상속
    applydamage
    gamestate
    DP
    매크로 지정자
    map을 vector로 복사
    Vector
    addonscreendebugmessage
    싱글턴
    템플릿
    motion matching
    additive animation
    const
    C++
    character animation
    enhanced input system
    actor 클래스
    blend pose
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
gbleem
const 키워드
상단으로

티스토리툴바