Reference declaration
기존에 존재하는 객체나 함수에 대한 별칭인 참조(reference)로 명명된 변수를 선언합니다.
목차 |
구문
참조 변수 선언은 선언자 가 다음과 같은 형태를 갖는 모든 단순 선언입니다
&
attr
(선택 사항)
declarator
|
(1) | ||||||||
&&
attr
(선택 사항)
declarator
|
(2) | (since C++11) | |||||||
D
를
decl-specifier-seq
S
에 의해 결정된 타입에 대한
lvalue reference
로 선언합니다.
D
를
decl-specifier-seq
S
에 의해 결정된 타입에 대한
rvalue reference
로 선언합니다.
| declarator | - | 참조 선언자, 배열 선언자 , 그리고 포인터 선언자 를 제외한 모든 선언자 (참조에 대한 참조, 참조의 배열, 참조에 대한 포인터는 존재하지 않음) |
| attr | - | (C++11부터) 속성 목록 |
참조는 유효한 객체나 함수를 참조하도록 초기화되어야 합니다: reference initialization 을 참조하십시오.
"void(에 cv 한정자가 있을 수 있음)에 대한 참조" 유형은 형성될 수 없습니다.
참조 타입은 최상위 수준에서
cv-qualified
될 수 없습니다; 선언에서 이를 위한 문법이 존재하지 않으며, typedef-name
또는
decltype
지정자,
(C++11부터)
또는
type template parameter
에 한정자가 추가되는 경우 무시됩니다.
참조는 객체가 아닙니다; 참조는 반드시 저장 공간을 차지하지는 않지만, 컴파일러가 원하는 의미론을 구현하기 위해 필요하다면 저장 공간을 할당할 수 있습니다 (예: 참조 타입의 비정적 데이터 멤버는 일반적으로 메모리 주소를 저장하는 데 필요한 크기만큼 클래스의 크기를 증가시킵니다).
참조는 객체가 아니기 때문에, 참조의 배열, 참조에 대한 포인터, 참조에 대한 참조는 존재하지 않습니다:
int& a[3]; // 오류 int&* p; // 오류 int& &r; // 오류
참조 축약템플릿이나 typedef에서 타입 조작을 통해 참조에 대한 참조를 형성하는 것이 허용되며, 이 경우 참조 축약 규칙이 적용됩니다: rvalue 참조에 대한 rvalue 참조는 rvalue 참조로 축약되며, 다른 모든 조합은 lvalue 참조를 형성합니다: typedef int& lref; typedef int&& rref; int n; lref& r1 = n; // type of r1 is int& lref&& r2 = n; // type of r2 is int& rref& r3 = n; // type of r3 is int& rref&& r4 = 1; // type of r4 is int&&
(이는 함수 템플릿에서
|
(C++11부터) |
Lvalue 참조
Lvalue 참조는 기존 객체의 별칭으로 사용될 수 있습니다 (선택적으로 다른 cv-qualification과 함께):
#include <iostream> #include <string> int main() { std::string s = "Ex"; std::string& r1 = s; const std::string& r2 = s; r1 += "ample"; // s를 수정함 // r2 += "!"; // 오류: const 참조를 통해 수정할 수 없음 std::cout << r2 << '\n'; // s를 출력하며, 현재 "Example"을 보유함 }
이들은 함수 호출에서 참조에 의한 전달(pass-by-reference) 의미를 구현하는 데에도 사용될 수 있습니다:
#include <iostream> #include <string> void double_string(std::string& s) { s += s; // 's'는 main()의 'str'과 같은 객체입니다 } int main() { std::string str = "Test"; double_string(str); std::cout << str << '\n'; }
함수의 반환 타입이 lvalue 참조일 때, 함수 호출 표현식은 lvalue 표현식이 됩니다:
#include <iostream> #include <string> char& char_number(std::string& s, std::size_t n) { return s.at(n); // string::at()은 char에 대한 참조를 반환합니다 } int main() { std::string str = "Test"; char_number(str, 1) = 'a'; // 함수 호출은 lvalue이므로 할당 가능합니다 std::cout << str << '\n'; }
Rvalue 참조Rvalue 참조는 임시 객체의 수명을 연장 하는 데 사용될 수 있습니다 (참고: const에 대한 lvalue 참조도 임시 객체의 수명을 연장할 수 있지만, 이를 통해 수정할 수는 없습니다):
이 코드 실행
#include <iostream> #include <string> int main() { std::string s1 = "Test"; // std::string&& r1 = s1; // 오류: lvalue에 바인딩할 수 없음 const std::string& r2 = s1 + s1; // 정상: const에 대한 lvalue 참조가 수명을 연장함 // r2 += "Test"; // 오류: const 참조를 통해 수정할 수 없음 std::string&& r3 = s1 + s1; // 정상: rvalue 참조가 수명을 연장함 r3 += "Test"; // 정상: non-const 참조를 통해 수정할 수 있음 std::cout << r3 << '\n'; } 더 중요하게는, 함수가 rvalue 참조와 lvalue 참조 오버로드 를 모두 가질 때, rvalue 참조 오버로드는 rvalue(prvalue와 xvalue 모두 포함)에 바인딩되고, lvalue 참조 오버로드는 lvalue에 바인딩됩니다:
이 코드 실행
#include <iostream> #include <utility> void f(int& x) { std::cout << "lvalue reference overload f(" << x << ")\n"; } void f(const int& x) { std::cout << "lvalue reference to const overload f(" << x << ")\n"; } void f(int&& x) { std::cout << "rvalue reference overload f(" << x << ")\n"; } int main() { int i = 1; const int ci = 2; f(i); // f(int&) 호출 f(ci); // f(const int&) 호출 f(3); // f(int&&) 호출 // f(int&&) 오버로드가 제공되지 않았다면 f(const int&)를 호출함 f(std::move(i)); // f(int&&) 호출 // rvalue reference 변수는 표현식에서 사용될 때 lvalue임 int&& x = 1; f(x); // f(int& x) 호출 f(std::move(x)); // f(int&& x) 호출 } 이는 적합한 경우 move constructors , move assignment 연산자, 그리고 다른 move-aware 함수들(예: std::vector::push_back() )이 자동으로 선택되도록 합니다. rvalue 참조는 xvalue에 바인딩될 수 있으므로 비임시 객체를 참조할 수 있습니다: int i2 = 42; int&& rri = std::move(i2); // i2에 직접 바인딩됩니다 이렇게 하면 더 이상 필요하지 않은 범위 내 객체에서 이동이 가능해집니다: std::vector<int> v{1, 2, 3, 4, 5}; std::vector<int> v2(std::move(v)); // v에 대한 rvalue 참조를 바인딩합니다 assert(v.empty()); 전달 참조포워딩 참조(Forwarding references)는 함수 인자의 값 범주(value category)를 보존하는 특별한 종류의 참조로, 전달(forward) 을 std::forward 를 통해 가능하게 합니다. 포워딩 참조는 다음 중 하나입니다:
1)
동일한 함수 템플릿의 cv-unqualified
type template parameter
에 대한 rvalue reference로 선언된 함수 템플릿의 함수 매개변수:
template<class T> int f(T&& x) // x는 전달 참조(forwarding reference)입니다 { return g(std::forward<T>(x)); // 따라서 전달(forward)될 수 있습니다 } int main() { int i; f(i); // 인수가 lvalue, f<int&>(int&) 호출, std::forward<int&>(x)는 lvalue f(0); // 인수가 rvalue, f<int>(int&&) 호출, std::forward<int>(x)는 rvalue } template<class T> int g(const T&& x); // x는 전달 참조가 아닙니다: const T는 cv-unqualified가 아닙니다 template<class T> struct A { template<class U> A(T&& x, U&& y, int* p); // x는 전달 참조가 아닙니다: T는 생성자의 // 타입 템플릿 매개변수가 아니지만, // y는 전달 참조입니다 };
2)
auto
&&
중괄호로 둘러싸인 초기화 목록에서 추론되는 경우를 제외하고
또는 클래스 템플릿의 템플릿 매개변수를 나타낼 때
클래스 템플릿 인수 추론
중인 경우
(C++17부터)
:
auto&& vec = foo(); // foo()는 lvalue 또는 rvalue일 수 있으며, vec은 전달 참조(forwarding reference)입니다 auto i = std::begin(vec); // 두 경우 모두 동작합니다 (*i)++; // 두 경우 모두 동작합니다 g(std::forward<decltype(vec)>(vec)); // 값 범주(value category)를 보존하며 전달합니다 for (auto&& x: f()) { // x는 전달 참조입니다; 이는 제네릭 코드에서 범위 기반 for 루프를 사용하는 일반적인 방법입니다 } auto&& z = {1, 2, 3}; // 전달 참조가 *아닙니다* (초기화 리스트에 대한 특별한 경우) 참고 항목 template argument deduction 및 std::forward . |
(C++11 이후) |
댕글링 참조
참조는 초기화 시 항상 유효한 객체나 함수를 가리키지만, 참조된 객체의 수명 이 끝났음에도 참조가 접근 가능한 상태로 남아 있는 ( dangling ) 프로그램을 만들 수 있습니다.
참조 타입의 표현식 expr 이 주어지고, 해당 참조가 나타내는 객체나 함수를 target 이라고 할 때:
- 만약 target 에 대한 포인터가 expr 의 평가 맥락에서 유효하다면 , 결과는 target 을 지정합니다.
- 그렇지 않으면, 동작은 정의되지 않습니다.
std::string& f() { std::string s = "Example"; return s; // s의 범위를 벗어남: // 소멸자가 호출되고 저장 공간이 해제됨 } std::string& r = f(); // 댕글링 참조 std::cout << r; // 정의되지 않은 동작: 댕글링 참조에서 읽기 std::string s = f(); // 정의되지 않은 동작: 댕글링 참조로부터 복사 초기화
rvalue 참조와 const에 대한 lvalue 참조는 임시 객체의 수명을 연장합니다(규칙과 예외 사항은 Reference initialization 참조).
참조된 객체가 소멸되었지만(예: 명시적 소멸자 호출), 저장 공간이 해제되지 않은 경우, 수명이 끝난 객체에 대한 참조는 제한된 방식으로 사용될 수 있으며, 동일한 저장 공간에 객체가 재생성되면 유효해질 수 있습니다(자세한 내용은 Access outside of lifetime 참조).
타입 접근 불가 참조
변환된 초기화식이 lvalue (until C++11) glvalue (since C++11) 이고, 이를 통해 객체가 type-accessible 하지 않은 경우, 해당 객체에 대한 참조를 바인딩하려는 시도는 정의되지 않은 동작을 초래합니다:
char x alignas(int); int& ir = *reinterpret_cast<int*>(&x); // 정의되지 않은 동작: // 초기화자가 char 객체를 참조함
호환되지 않는 호출 참조
변환된 초기화식이 lvalue인 (C++11까지) glvalue인 (C++11부터) 함수에 대한 참조를 바인딩하려고 시도할 때, 해당 타입이 함수 정의의 타입과 호환 가능(call-compatible) 하지 않으면 정의되지 않은 동작이 발생합니다:
void f(int); using F = void(float); F& ir = *reinterpret_cast<F*>(&f); // 정의되지 않은 동작: // 초기화자가 void(int) 함수를 참조함
참고 사항
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_rvalue_references
|
200610L
|
(C++11) | Rvalue references |
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 453 | C++98 | 참조를 어떤 객체나 함수에 바인딩할 수 없는지 불분명했음 | 명확히 규정됨 |
| CWG 1510 | C++11 | decltype 피연산자에서 cv-한정된 참조를 형성할 수 없었음 | 허용됨 |
| CWG 2550 | C++98 | 매개변수가 " void 에 대한 참조" 타입을 가질 수 있었음 | 허용되지 않음 |
| CWG 2933 | C++98 | 댕글링 참조 접근 동작이 불분명했음 | 명확히 규정됨 |
외부 링크
| Thomas Becker, 2013 - C++ Rvalue References Explained |