증감 연산자
증감 연산자 (Increment/Decrement operators)
증감 연산자는 객체의 값을 1만큼 늘리거나 줄여요. 전위(++a)와 후위(a++)의 차이를 정확히 이해하는 게 이 연산자의 핵심이에요.
본문
| 연산자 이름 | 문법 | 오버로드 가능 | 클래스 안 프로토타입 예 (T) | 클래스 밖 프로토타입 예 (T) |
|---|---|---|---|---|
| 전위 증가 (pre-increment) | ++a |
예 | T& T::operator++(); |
T& operator++(T& a); |
| 전위 감소 (pre-decrement) | --a |
예 | T& T::operator--(); |
T& operator--(T& a); |
| 후위 증가 (post-increment) | a++ |
예 | T T::operator++(int); |
T operator++(T& a, int); |
| 후위 감소 (post-decrement) | a-- |
예 | T T::operator--(int); |
T operator--(T& a, int); |
참고:
- 내장 연산자의 전위 버전은 참조를 돌려주고 후위 버전은 값을 돌려줘요. 전형적인 사용자 정의 오버로드도 내장처럼 쓰일 수 있도록 같은 패턴을 따라요. 다만 사용자 정의 오버로드에서는 반환 타입으로 어떤 타입이든(
void포함) 쓸 수 있어요. int매개변수는 전위 버전과 후위 버전을 구분하는 데 쓰는 더미(dummy) 매개변수예요. 사용자 정의 후위 연산자를 호출할 때 그 매개변수에 전달되는 값은 항상 0이에요. 다만 함수 호출 표기법(예:a.operator++(2)나operator++(a, 2))으로 연산자를 호출하면 바꿀 수 있어요.
전위 연산자 (Prefix operators)
전위 증감 표현식은 다음 형태를 가져요:
| 형태 | 설명 |
|---|---|
++expression |
(1) 전위 증가 |
--expression |
(2) 전위 감소 |
내장 전위 연산자
- 표현식
++x는x += 1과 동등해요. 다음 예외가 있어요:
- 표현식의 타입이 (가능하면
volatile한정된)bool이면expression은true로 설정돼요. (until C++17) 이런 증가는 폐기 예정이었어요. - C++17부터 표현식의 타입이 (가능하면 cv 한정된)
bool이면 프로그램은 ill-formed예요. - C++20부터 표현식의 타입이
volatile한정이면 그 증가는 폐기 예정이에요.
- 표현식
--x는x -= 1과 동등해요. 다음 예외가 있어요:
- 표현식의 타입이 (가능하면 cv 한정된)
bool이면 프로그램은 ill-formed예요. - C++20부터 표현식의 타입이
volatile한정이면 그 감소는 폐기 예정이에요.
오버로드 (전위)
사용자 정의 연산자와의 오버로드 결정에서, bool이 아닌 모든 (선택적으로 volatile 한정된) 산술 타입 A와, (선택적으로 cv 한정된) 객체 타입에 대한 모든 (선택적으로 volatile 한정된) 포인터 P에 대해 다음 함수 시그니처가 참여해요:
A& operator++(A&)
bool& operator++(bool&)
P& operator++(P&)
A& operator--(A&)
P& operator--(P&)
(bool& operator++(bool&)는 폐기 예정이었으며 until C++17)
후위 연산자 (Postfix operators)
후위 증감 표현식은 다음 형태를 가져요:
| 형태 | 설명 |
|---|---|
expression++ |
(1) 후위 증가 |
expression-- |
(2) 후위 감소 |
내장 후위 연산자
후위 증가·감소의 결과는 (수정 전의) expression에 lvalue-to-rvalue 변환을 적용한 값이에요. 결과의 타입은 expression 타입의 cv 비한정 버전이에요.
expression이 (가능하면 cv 한정된) bool(C++17 이후)이 아닌 산술 타입의 수정 가능한 lvalue이거나, 완전 객체 타입에 대한 포인터가 아니면 프로그램은 ill-formed예요.
표현식의 타입이
volatile한정이면 그 증가·감소는 폐기 예정이에요. (C++20 이후)
expression의 값은 전위++연산자의 피연산자인 것처럼 수정돼요.expression의 값은 전위--연산자의 피연산자인 것처럼 수정돼요.
후위 증가·감소의 값 계산은 expression의 수정보다 먼저 시퀀스된다(sequenced before)돼요. 불확정적으로 시퀀스되는(indeterminately-sequenced) 함수 호출과 관련해서, 후위 증가·감소의 연산은 단일 평가예요.
오버로드 (후위)
사용자 정의 연산자와의 오버로드 결정에서, bool이 아닌 모든 (선택적으로 volatile 한정된) 산술 타입 A와, (선택적으로 cv 한정된) 객체 타입에 대한 모든 (선택적으로 volatile 한정된) 포인터 P에 대해 다음 함수 시그니처가 참여해요:
A operator++(A&, int)
bool operator++(bool&, int)
P operator++(P&, int)
A operator--(A&, int)
P operator--(P&, int)
(bool operator++(bool&, int)는 폐기 예정이었으며 until C++17)
예제
#include <iostream>
int main()
{
int n1 = 1;
int n2 = ++n1;
int n3 = ++ ++n1;
int n4 = n1++;
// int n5 = n1++ ++; // error
// int n6 = n1 + ++n1; // undefined behavior
std::cout << "n1 = " << n1 << '\n'
<< "n2 = " << n2 << '\n'
<< "n3 = " << n3 << '\n'
<< "n4 = " << n4 << '\n';
}
출력:
n1 = 5
n2 = 2
n3 = 4
n4 = 4
이 예제를 따라가 보면, n1은 1에서 시작해 ++n1으로 2가 되고 n2에 2가 저장돼요. ++ ++n1은 ++(++n1)이라 n1이 3→4가 되고 n3은 4예요. n1++은 n1(현재 4)을 n4에 저장하고 n1을 5로 늘려요. 그래서 n1=5, n4=4가 돼요. n1++ ++은 후위 증가 결과가 prvalue라 또 증가시킬 수 없어 오류고, n1 + ++n1은 시퀀싱 위반이라 정의되지 않은 동작이에요.
참고 (Notes)
부작용 때문에 내장 증가·감소 연산자는 시퀀싱 규칙을 위반해 정의되지 않은 동작을 만들지 않도록 조심해서 써야 해요.
후위 증가·감소 동안에는 객체의 임시 복사본이 만들어지므로, 반환 값을 쓰지 않는 상황에서는 보통 전위 증가·감소가 더 효율적이에요.
표준 라이브러리
증감 연산자는 표준 라이브러리의 많은 타입에 대해 오버로드되어 있어요. 특히 모든 LegacyIterator는 operator++를, 모든 LegacyBidirectionalIterator는 operator--를 오버로드해요. 그 연산자가 특정 반복자에 대해 no-op이라도요.
산술 타입에 대한 오버로드:
std::atomic<T>— 원자 값을 1만큼 증가·감소std::chrono::duration— tick count 증가·감소
반복자 타입에 대한 오버로드:
std::raw_storage_iterator,std::istream_iterator,std::istreambuf_iterator— 반복자 전진std::reverse_iterator,std::move_iterator— 증감·가감std::front_insert_iterator,std::back_insert_iterator,std::insert_iterator,std::ostream_iterator,std::ostreambuf_iterator— no-opstd::regex_iterator— 다음 일치로 전진std::regex_token_iterator— 다음 부분 일치로 전진
결함 보고 (Defect reports)
다음은 이전에 발행된 C++ 표준에 소급 적용된, 동작을 바꾸는 결함 보고예요.
| DR | 적용 대상 | 발행 당시 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 2855 | C++98 | 일반 산술 변환이 내장 전위 증가·감소에는 적용되는데 후위에는 적용되지 않았음¹ | 후위에도 적용됨 |
| CWG 2901 | C++98 | 내장 후위 증가·감소에 lvalue-to-rvalue 변환이 적용되지 않았음 | 적용됨 |
¹전위 ++x는 x += 1과 동등해서 일반 산술 변환이 적용될 수 있어요(즉 decltype(x)와 int 사이의 공통 타입을 만들죠). 반면 후위 x++의 효과는 단순히 "x에 1을 더하는 것"이라 이항 연산자가 없어 일반 산술 변환이 일어나지 않아요.
더 알아보기 (Learn more)
- 연산자 우선순위 — 각 연산자의 우선순위와 결합성을 정리한 표예요.
- 연산자 오버로딩 — 사용자 정의 타입에서 연산자를 재정의하는 방법이에요.