std :: reference_wrapper와 간단한 포인터의 차이점은 무엇입니까?


99

왜 필요가 std::reference_wrapper있습니까? 어디에서 사용해야합니까? 간단한 포인터와 어떻게 다릅니 까? 성능이 간단한 포인터와 어떻게 비교됩니까?


4
기본적으로 .대신 사용하는 포인터 입니다->
MM

5
@MM 아니요, 사용 .은 제안한 방식으로 작동하지 않습니다 (어떤 시점에서 오퍼레이터 도트 제안이 채택되고 통합되지 않는 한 :))
Columbo

3
새로운 C ++로 작업해야 할 때 나를 불행하게 만드는 것은 이와 같은 질문입니다.
Nils 19

Columbo를 추적하기 위해 std :: reference_wrapper는 get()멤버 함수 또는 기본 형식으로의 암시 적 변환과 함께 사용됩니다 .
Max Barraclough

답변:


88

std::reference_wrapper템플릿과 함께 사용하면 유용합니다. 객체에 대한 포인터를 저장하여 객체를 래핑하고 일반적인 의미를 모방하면서 재 할당 및 복사를 허용합니다. 또한 특정 라이브러리 템플릿에 개체 대신 참조를 저장하도록 지시합니다.

펑터를 복사하는 STL의 알고리즘을 고려하십시오. 펑터 자체 대신 펑터를 참조하는 참조 래퍼를 전달하면 해당 복사를 방지 할 수 있습니다.

unsigned arr[10];
std::mt19937 myEngine;
std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine's state

이것은 작동하기 때문에 ...

  • reference_wrappers 오버로드operator() 이므로 참조하는 함수 객체처럼 호출 할 수 있습니다.

    std::ref(myEngine)() // Valid expression, modifies myEngines state
  • … (비) 일반적인 참조와 달리 복사 (및 할당) reference_wrappers는 pointee를 할당합니다.

    int i, j;
    auto r = std::ref(i); // r refers to i
    r = std::ref(j); // Okay; r refers to j
    r = std::cref(j); // Error: Cannot bind reference_wrapper<int> to <const int>

참조 래퍼를 복사하는 것은 포인터를 복사하는 것과 거의 동일합니다. 사용에 내재 된 모든 함수 호출 (예 :에 대한 호출 operator())은 한 줄짜리이므로 인라인되어야합니다.

reference_wrapper들로 만들어집니다 std::refstd::cref :

int i;
auto r = std::ref(i); // r is of type std::reference_wrapper<int>
auto r2 = std::cref(i); // r is of type std::reference_wrapper<const int>

template 인자는 참조 된 객체의 유형과 cv-qualification을 지정합니다. r2를 참조하고에 const int대한 참조 만 생성합니다 const int. const펑터가있는 참조 래퍼에 대한 호출은 const멤버 함수 만 호출 합니다 operator().

Rvalue 이니셜 라이저는 허용하면 득보다 해를 끼칠 수 있으므로 허용되지 않습니다. rvalue는 어쨌든 이동 될 것이기 때문에 (그리고 부분적으로 피할 수 있는 보장 된 복사 제거 와 함께 ) 의미 체계를 개선하지 않습니다. 참조 래퍼가 pointee의 수명을 연장하지 않기 때문에 매달린 포인터를 도입 할 수 있습니다.

도서관 상호 작용

앞에서 언급했듯이 해당 인수를 a를 통해 전달하여 make_tuple결과 tuple에 참조를 저장 하도록 지시 할 수 있습니다 reference_wrapper.

int i;
auto t1 = std::make_tuple(i); // Copies i. Type of t1 is tuple<int>
auto t2 = std::make_tuple(std::ref(i)); // Saves a reference to i.
                                        // Type of t2 is tuple<int&>

forward_as_tuple다음 과 약간 다릅니다 . 여기서, rvalue는 인수로 사용할 수 없습니다.

std::bind동일한 동작을 보여줍니다. 인수를 복사하지 않고 reference_wrapper. 해당 인수 (또는 functor!)를 복사 할 필요는 bind없지만 -functor가 사용되는 동안 범위에 남아있는 경우 유용합니다 .

일반 포인터와의 차이점

  • 추가적인 수준의 구문 적 간접 지정은 없습니다. 포인터가 참조하는 객체에 대한 lvalue를 얻으려면 포인터를 역 참조해야합니다. reference_wrapper에는 암시 적 변환 연산자 가 있으며 래핑 된 객체처럼 호출 될 수 있습니다.

    int i;
    int& ref = std::ref(i); // Okay
  • reference_wrappers는 포인터와 달리 null 상태가 아닙니다. 그것들은 참조 또는 다른reference_wrapper 것으로 초기화되어야 합니다 .

    std::reference_wrapper<int> r; // Invalid
  • 유사점은 얕은 복사 의미 체계입니다. 포인터와 reference_wrappers를 다시 할당 할 수 있습니다.


std::make_tuple(std::ref(i));뛰어난 std::make_tuple(&i);어떤 방법으로?
Laurynas Lazauskas 2014

6
@LaurynasLazauskas 다릅니다. 후자 i는 참조가 아니라에 대한 포인터를 저장 합니다.
Columbo 2014

흠 ..이 둘을 구분할 수없는 것 같아요. 제가 원하는대로 ... 음, 감사합니다.
Laurynas Lazauskas 2014

@Columbo null 상태가없는 경우 참조 래퍼 배열은 어떻게 가능합니까? 배열은 일반적으로 모든 요소가 null 상태로 설정된 상태로 시작하지 않습니까?
anatolyg 2014

2
@anatolyg 배열을 초기화하는 데 방해가되는 것은 무엇입니까?
Columbo 2014

27

최소한 두 가지 동기 부여 목적이 있습니다 std::reference_wrapper<T>.

  1. 함수 템플릿에 값 매개 변수로 전달 된 객체에 참조 의미를 부여하는 것입니다. 예를 들어, std::for_each()값으로 함수 객체 매개 변수 를 사용 하는 전달하려는 큰 함수 객체가있을 수 있습니다 . 개체 복사를 방지하려면 다음을 사용할 수 있습니다.

    std::for_each(begin, end, std::ref(fun));

    표현식 std::reference_wrapper<T>에 대한 인수를 전달 하는 std::bind()것은 값이 아닌 참조로 인수를 바인딩하는 데 매우 일반적입니다.

  2. 해당 튜플 요소 std::reference_wrapper<T>와 함께 사용하면 std::make_tuple()a T&가 아닌 a가됩니다 T.

    T object;
    f(std::make_tuple(1, std::ref(object)));

첫 번째 경우에 대한 코드 예제를 제공 할 수 있습니까?
user1708860

1
@ user1708860 : 당신은 주어진 것 이외의 다른 것을 의미합니다 ...?
Dietmar Kühl 2014

나는 std :: ref (fun)과 함께 사용되는 실제 코드를 의미합니다. 사용 방법을 이해하지 못하기 때문입니다 (재미가 함수가 아니라 객체가 아닌 경우 ...)
user1708860 2014

2
@ user1708860 : 예, fun함수가 아닌 함수 객체 (즉, 함수 호출 연산자가있는 클래스의 객체) 일 가능성 이 높습니다 fun. 실제 함수 인 std::ref(fun)경우 목적이없고 코드를 잠재적으로 느리게 만듭니다.
Dietmar Kühl 2014

23

자체 문서화 코드의 또 다른 차이점은을 사용하면 reference_wrapper본질적으로 객체의 소유권을 거부한다는 것입니다. 반대로, a unique_ptr는 소유권을 주장하지만, 베어 포인터는 소유 할 수도 있고 아닐 수도 있습니다 (많은 관련 코드를 살펴 보지 않고는 알 수 없습니다).

vector<int*> a;                    // the int values might or might not be owned
vector<unique_ptr<int>> b;         // the int values are definitely owned
vector<reference_wrapper<int>> c;  // the int values are definitely not owned

3
C ++ 11 이전 코드가 아닌 경우 첫 번째 예제는 인덱스 기반 캐시 조회와 같이 소유되지 않은 선택적 값을 암시해야합니다. 표준이 null이 아닌 소유 값 (고유 및 공유 변형)을 나타내는 표준을 제공한다면 좋을 것입니다.
Bwmat

어쨌든 베어 포인터가 거의 항상 값을 빌리는 C ++ 11에서는 중요하지 않을 수 있습니다.
Elling

reference_wrapper비 소유라는 것이 분명하기 때문일뿐만 아니라 (비 소유 nullptr없이) 될 수 없기 때문에 사용자가 통과 할 수 없다는 것을 알고 ( 비 소유 없이) 통과 nullptr할 필요가 없다는 것을 알기 때문에 원시 포인터보다 우수 합니다. 그것을 확인하십시오.
underscore_d

19

컨테이너에서 사용할 수 있도록 참조를 둘러싼 편리한 래퍼로 생각할 수 있습니다.

std::vector<std::reference_wrapper<T>> vec; // OK - does what you want
std::vector<T&> vec2; // Nope! Will not compile

그것은 기본적으로 A의 CopyAssignable버전 T&. 참조를 원할 때마다 할당 가능해야 std::reference_wrapper<T>하거나 도우미 기능을 사용해야 합니다 std::ref(). 또는 포인터를 사용하십시오.


기타 단점 : sizeof:

sizeof(std::reference_wrapper<T>) == sizeof(T*) // so 8 on a 64-bit box
sizeof(T&) == sizeof(T) // so, e.g., sizeof(vector<int>&) == 24

그리고 비교 :

int i = 42;
assert(std::ref(i) == std::ref(i)); // ok

std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error

1
@LaurynasLazauskas 래퍼에 포함 된 함수 개체를 직접 호출 할 수 있습니다. 그것은 또한 내 대답에 설명되어 있습니다.
Columbo 2014

2
참조 구현은 내부의 포인터 일 뿐이므로 래퍼가 간접 또는 성능 저하를 추가하는 이유를 이해할 수 없습니다.
Riga

4
릴리스 코드에 관해서는 단순한 참조보다 더 간접적이어서는 안됩니다
Riga

3
컴파일러가 간단한 reference_wrapper코드 를 인라인하여 포인터 나 참조를 사용하는 코드와 동일하게 만들 것으로 예상합니다 .
David Stone

4
@LaurynasLazauskas : std::reference_wrapper개체가 null이 아님을 보장합니다. 반원을 생각해보십시오 std::vector<T *>. 이 객체가 nullptr벡터에 a 를 저장할 수 있는지 확인하려면 모든 클래스 코드를 검사해야하는 반면에서는 std::reference_wrapper<T>유효한 객체가 보장됩니다.
David Stone
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.