포스트

(상수) const 와 constexpr

const상수를 선언하는 제한자(qualifier)입니다. 즉, 한 번 초기화된 const 변수의 값은 변경할 수 없습니다.

#define으로 상수처럼 사용할 수 있으나, const로 선언하는 경우에는 데이터형을 명시적으로 지정 할 수 있고, 활동 범위 규칙으로 특정 함수나 파일에서만 사용할 수 있도록 제한할 수 있습니다.

1
const int x = 10;  // x는 10으로 초기화되고 이후에 변경할 수 없습니다.

const 키워드는 포인터, 레퍼런스, 멤버 변수, 멤버 함수, 지역 변수에 붙을 수 있습니다.


포인터에서의 const

포인터와 함께 사용될 때 const의 위치에 따라 의미가 달라집니다.

(const) int (const) * (const) ptr;

1. 포인터가 가리키는 값이 상수인 경우

const int * ptr, int const * ptr

별 앞에 const가 붙는 경우, 상수형 데이터를 가리키는 포인터로, 포인터가 가리키는 값을 변경할 수 없습니다. (const intint const 는 같습니다.)

단, 포인터 주소는 변경 가능합니다.

1
2
3
const int* ptr = &x;  // ptr은 상수를 가리키는 포인터입니다.
*ptr = 20;            // 불가능: ptr이 가리키는 값은 변경할 수 없습니다.
ptr = &y;             // 가능: ptr의 메모리 주소를 변경할 수 있습니다.

2. 포인터 자체가 상수인 경우

int * const ptr

별 뒤에 const가 붙는 경우, 포인터(int*)를 상수화 시키므로 포인터가 가리키는 메모리 위치를 변경할 수 없습니다.

단, 데이터 값은 변경할 수 있습니다.

1
2
3
4
int x = 5, y = 10;
int* const ptr = &x;  // ptr은 상수 포인터입니다.
*ptr = 20;            // 가능: ptr이 가리키는 값은 변경할 수 있습니다.
ptr = &y;             // 불가능: ptr의 메모리 주소를 변경할 수 없습니다.

3. 포인터와 가리키는 값 모두 상수인 경우

const int * const ptr

const는 포인터의 앞과 뒤에 두 개 올 수 있습니다. 이 때에는 상수형 데이터를 가리키는 포인터를 상수화 시킨 것으로, 주소와 값 둘 다 변경할 수 없습니다.

1
**const int* const ptr = &x;**

멤버 함수에서의 const

function () const

멤버 함수 뒤에 const를 붙이면 해당 함수 내에서 멤버 변수를 수정할 수 없게 됩니다. 이를 통해 읽기 전용 멤버 함수를 만들 수 있습니다.

이 때, 선언부와 정의부 둘 다 const가 써있어야 합니다.


constexpr 이란?

constexpr는 C++11부터 도입된 키워드로, 컴파일 타임에 상수 표현식을 계산할 수 있게 해줍니다.

constexpr 변수

constexpr은 컴파일 타임에 계산되는 표현식이므로, 변수 선언과 동시에 초기화 될 수 있는 리터럴 타입(LiteralType)이어야 합니다.

1
constexpr int x = 10 * 10; // 컴파일 타임에 100으로 계산됩니다.

클래스에서의 사용

조금 특이한 것은 클래스 타입리터럴 타입이 될 수 있다는 것입니다.

리터럴 타입 클래스의 조건은 조금 복잡할 수 있지만, 정리하자면 다음과 같습니다.

  • constexpr 생성자를 가지고 있어야 합니다.
  • 소멸자가 컴파일러에 의해 자동으로 생성되거나, 디폴트(default) 소멸자여야 합니다.
  • 모든 멤버 변수리터럴 타입이어야 합니다.
  • 상속 받지 않아야 합니다.

이런 조건을 만족한다면, 클래스에도 constexpr을 붙일 수 있습니다.

constexpr의 특이한 사용

constexpr컴파일 타임 상수이고, 클래스에도 constexpr을 붙일 수 있으므로 다음과 같은 특이한 코드도 가능하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;

class MyClass
{
public:
	int x; // 리터럴 타입
	constexpr MyClass(int val) : x(val) {} // constexpr 생성자
	~MyClass() = default; // 디폴트(default) 소멸자
};

int main()
{
	constexpr MyClass obj1(10); // 컴파일 타임에 생성
	int arr1[obj1.x] = { 0 }; // 컴파일 타임에 크기 결정

	MyClass obj2(20); // 런타임에 생성
	// int arr2[obj2.x] = { 0 }; // obj2는 런타임에 생성되므로, 컴파일 타임 상수로 사용할 수 없습니다.

	cout << "\n컴파일 타임 상수로 배열 크기 결정\n";
	for (auto& n : arr1)
	{
		cout << n << ' ';
	}
	cout << "\n\n";
}

constexpr의 특이한 사용


constexpr 함수

constexpr는 함수에서도 사용할 수 있습니다. 그 경우, 해당 함수는 컴파일 타임에 실행 가능한 함수여야 합니다.

1
2
3
4
constexpr int square(int x)
{
    return x * x;
}

constexpr의 특이한 사용

다만, constexpr 함수를 사용하려는 경우, 관련된 선언에도 constexpr 지정자가 있어야 합니다.

그리고 constexpr컴파일 타임 상수이기 때문에, 그 반환 값도 컴파일 타임 상수가 되고, 따라서 다음과 같은 사용도 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

constexpr int square(int a)
{
	return a * a;  // 컴파일 타임에 계산됩니다.
}

int main()
{
	constexpr int a = 1;
	constexpr int b = 2;
	int arr[square(a * b + 10)] = { 0 };  // 컴파일 타임에 계산됩니다.

	cout << "\n컴파일 타임 상수로 배열 크기 결정\n";
	int i = 0;
	for (auto& n : arr)
	{
		if (i > (a * b + 10))
		{
			i = 0;
			cout << '\n';
		}
		cout << n << ' ';
		i++;
	}
	cout << "\n\n";
}

constexpr의 특이한 사용2

이 기사는 저작권자의 CC BY-NC-ND 4.0 라이센스를 따릅니다.