`return` 문(return statement)
return 문(return statement)
함수가 할 일을 끝내고, 결과 값(있을 경우)을 호출한 쪽으로 돌려줘야 할 때 쓰는 문이 return이에요. return은 현재 함수를 종료하고 지정된 값을 호출자에게 반환해요.
출처: cppreference
본문
문법
attr(optional) return expression(optional) ; |
(1) |
attr(optional) return braced-init-list ; |
(2) (C++11부터) |
attr(optional) co_return expression(optional) ; |
(3) (C++20부터) |
attr(optional) co_return braced-init-list ; |
(4) (C++20부터) |
동작 방식(Explanation)
- 표현식을 평가하고, 반환 타입으로의 암시적 변환(implicit conversion)을 거친 다음, 현재 함수를 종료하고 그 결과를 호출자에게 반환해요. 반환 타입이 (cv 한정될 수 있는)
void인 함수에서는 표현식을 생략할 수 있고, 생성자와 소멸자에서는 표현식을 쓸 수 없어요.
3,4) 코루틴(coroutine)에서는 마지막 중단 지점에서 return 대신 키워드 co_return을 써야 해요.
표현식 또는 braced-init-list(C++11부터)(있을 경우)를 return 문의 피연산자(operand)라고 불러요.
| 함수 호출 결과의 복사 초기화와 표현식 끝에서의 모든 임시 객체 파괴 사이에 시퀀스 포인트(sequence point)가 있다. | (C++11까지) |
|---|---|
함수 호출 결과의 복사 초기화는 표현식 끝에서의 모든 임시 객체 파괴보다 앞서 서열화되고(sequenced-before), 이는 다시 return 문을 둘러싼 블록의 지역 변수 파괴보다 앞서 서열화된다. |
(C++11부터) |
함수의 반환 타입이 참조 타입이고 return 문 (1,2)이 반환된 참조를 임시 표현식의 결과에 바인딩하면, 프로그램은 ill-formed다. |
(C++26부터) |
제어 흐름이 return 문을 만나지 않고 다음 함수의 끝에 도달하면 return;이 실행돼요.
- 반환 타입이 (cv 한정될 수 있는)
void인 함수 - 생성자
- 소멸자
- 반환 타입이 (cv 한정될 수 있는)
void인 함수의 함수 try 블록
제어 흐름이 main 함수의 끝에 도달하면 return 0;이 실행돼요.
값을 반환하는 함수에서 main 함수와 일부 코루틴(C++20부터)을 제외하고, return 문 없이 끝까지 흘러내려가는 것은 미정의 동작(undefined behavior)이에요.
(또는 cv 한정될 수 있는) void를 반환하는 함수에서, 표현식의 타입이 (또는 cv 한정될 수 있는) void라면 표현식을 가진 return 문을 쓸 수 있어요.
함수의 반환 타입이 자리 표시자 타입(placeholder type)으로 지정되면, 반환 값으로부터 타입이 추론(deduce)돼요. decltype(auto)를 쓰면 타입 추론이 엔티티가 될 수 있는 표현식을 엔티티로 취급해요. |
(C++14부터) |
|---|
주의할 점(Notes)
값을 반환하면 복사 생략(copy elision)을 쓰지 않는 한 임시 객체의 생성과 복사/이동이 수반될 수 있어요. 복사/이동의 조건은 구체적으로 다음과 같아요.
지역 변수와 매개변수의 자동 이동(Automatic move)
| 표현식이 (괄호로 묶을 수 있는) 식별자 표현식(identifier expression)으로서, 자동 저장 기간(duration)을 가진 변수를 이름 짓고 그 타입이 - 비휘발성 객체 타입, 또는 - 비휘발성 객체 타입에 대한 rvalue 참조(C++20부터)이면서, 그 변수가 가장 안쪽 둘러싸는 함수나 람다의 본문 또는 매개변수로 선언되어 있으면, 그 표현식은 이동 자격(move-eligible)이 있다. | (C++11부터) |
|---|---|
표현식이 이동 자격이 있으면, 반환 값의 초기화에 쓸 생성자를 선택하거나(co_return의 경우 promise.return_value() 오버로드를 선택, C++20부터) 오버로드 해석(overload resolution)이 두 번 수행돼요. 첫 번째는 표현식이 rvalue인 것처럼(C++11부터이므로 이동 생성자를 선택할 수 있음), 그리고 첫 번째 오버로드 해석이 실패하면, 또는 성공해도 이동 생성자를 선택하지 않았으면, 표현식을 lvalue로 간주하고 평소처럼 오버로드 해석이 수행돼요(그래서 복사 생성자를 선택할 수 있음). |
(C++11부터)(C++23까지) |
| 표현식이 이동 자격이 있으면, 그것은 xvalue로 취급돼요(그래서 오버로드 해석이 이동 생성자를 선택할 수 있음). | (C++23부터) |
보장된 복사 생략(Guaranteed copy elision)
| 표현식이 prvalue이면, 결과 객체는 그 표현식에 의해 직접 초기화돼요. 타입이 일치하면 복사나 이동 생성자가 관여하지 않아요. | (C++17부터) |
|---|
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_implicit_move |
202207L |
(C++23) | 더 단순해진 암시적 이동(Simpler implicit move) |
예제
return의 여러 형태와, 이동 자격 있는 표현식이 어떻게 동작하는지 확인해 볼게요.
#include <iostream>
#include <string>
#include <utility>
void fa(int i)
{
if (i == 2)
return;
std::cout << "fa(" << i << ")\n";
} // 암시적인 return;
int fb(int i)
{
if (i > 4)
return 4;
std::cout << "fb(" << i << ")\n";
return 2;
}
std::pair<std::string, int> fc(const char* p, int x)
{
return {p, x};
}
void fd()
{
return fa(10); // fa(10)은 void 표현식
}
int main()
{
fa(1); // 인자를 출력하고 반환
fa(2); // i == 2일 때 아무것도 하지 않고 그냥 반환
int i = fb(5); // 4를 반환
i = fb(i); // 인자를 출력하고 2를 반환
std::cout << "i = " << i << '\n'
<< "fc(~).second = " << fc("Hello", 7).second << '\n';
fd();
}
struct MoveOnly
{
MoveOnly() = default;
MoveOnly(MoveOnly&&) = default;
};
MoveOnly move_11(MoveOnly arg)
{
return arg; // OK. 암시적 이동
}
MoveOnly move_11(MoveOnly&& arg)
{
return arg; // C++20부터 OK. 암시적 이동
}
MoveOnly&& move_23(MoveOnly&& arg)
{
return arg; // C++23부터 OK. 암시적 이동
}
출력:
fa(1) fb(4) i = 2 fc(~).second = 7 fa(10)
fa(2)는 아무것도 출력하지 않고 바로 돌아오고, fb(5)는 4를, fb(i)는 2를 반환해서 i = 2가 출력돼요. return {p, x}로 초기화 목록을 돌려주는 fc도 확인할 수 있고요.
더 알아보기
- 암시적 변환(implicit casts): 반환 값이 함수 반환 타입으로 변환되는 규칙.
- 복사 생략(copy elision): C++17 이후 보장된 복사 생략.
- 코루틴(coroutines):
co_return이 쓰이는 마지막 중단 지점.