Development Tip

C ++ 참조-단지 구문상의 설탕일까요?

yourdevel 2020. 11. 19. 22:06
반응형

C ++ 참조-단지 구문상의 설탕일까요?


C ++ 참조는 구문상의 설탕 일 뿐입니 까, 아니면 특정 경우에 속도 향상을 제공합니까?

예를 들어, 포인터 별 호출은 어쨌든 복사본을 포함하며 참조 별 호출에 대해서도 마찬가지입니다. 기본 메커니즘은 동일한 것으로 보입니다.

편집 : 약 6 개의 답변과 많은 댓글 후. 나는 여전히 의견 참조가 단지 통역 적 설탕이라고 생각합니다. 사람들이 똑바로 예 또는 아니오로 대답 할 수 있고 누군가가 받아 들여진 대답을 할 수 있다면?


참조를 다음과 같은 포인터로 가정합니다.

  1. NULL 일 수 없습니다.
  2. 초기화되면 다른 개체를 다시 가리킬 수 없습니다.
  3. 이를 사용하려고하면 암시 적으로 역 참조됩니다.

    int a = 5;
    int &ra = a;
    int *pa = &a;
    
    ra = 6;
    
    (*pa) = 6;
    

분해에서 보이는 것처럼 여기에 :

    int a = 5;
00ED534E  mov         dword ptr [a],5  
    int &ra = a;
00ED5355  lea         eax,[a]  
00ED5358  mov         dword ptr [ra],eax  
    int *pa = &a;
00ED535B  lea         eax,[a]  
00ED535E  mov         dword ptr [pa],eax  

    ra = 6;
00ED5361  mov         eax,dword ptr [ra]  
00ED5364  mov         dword ptr [eax],6  

    (*pa) = 6;
00ED536A  mov         eax,dword ptr [pa]  
00ED536D  mov         dword ptr [eax],6  

참조에 대한 할당은 역 참조 된 포인터에 할당하는 것과 컴파일러 관점에서 볼 때 동일합니다. 보시다시피 그들 사이에는 차이가 없습니다 (지금은 컴파일러 최적화에 대해 이야기하고 있지 않습니다) 그러나 위에서 언급했듯이 참조는 null 일 수 없으며 포함 된 내용에 대해 더 강력한 보증을 제공합니다.

필자는 nullptr유효한 값, 다시 가리켜 야하는 값 또는 전달할 다른 유형의 값 (예 : 인터페이스 유형에 대한 포인터)으로 필요하지 않는 한 참조를 사용하는 것을 선호합니다 .


참조는 포인터보다 더 강력한 보증을 제공하므로 컴파일러가보다 적극적으로 최적화 할 수 있습니다. 최근에 함수 참조를 통해 GCC 인라인 다중 중첩 호출을 완벽하게 보았지만 함수 포인터를 통한 단일 호출은 아닙니다 (포인터가 항상 동일한 함수를 가리키고 있음을 증명할 수 없기 때문입니다).

참조가 어딘가에 저장되면 일반적으로 포인터와 동일한 공간을 차지합니다. 다시 말하지만, 포인터처럼 사용될 것이라는 말은 아닙니다. 컴파일러는 참조가 바인딩 된 객체를 안다면 잘 뚫을 수 있습니다.


컴파일러는 포인터가 널이 아닌 것으로 가정 할 수 없습니다. 코드를 최적화 할 때 포인터가 널이 아님을 증명하거나 널 (잘 정의 된 컨텍스트에서) 가능성을 설명하는 프로그램을 내 보내야합니다.

마찬가지로 컴파일러는 포인터가 값을 변경하지 않는다고 가정 할 수 없습니다. (잘 정의 된 컨텍스트에서 중요한 경우를 상상하는 데 어려움이 있지만 포인터가 유효한 개체를 가리키는 것으로 가정 할 수도 없습니다)

반면에 참조가 포인터로 구현되었다고 가정하면 컴파일러 여전히 null이 아니라고 가정하고 가리키는 위치를 변경하지 않고 유효한 개체를 가리킬 수 있습니다.


참조는 참조에 대해 수행 할 수없고 정의 된 동작이 있다는 점에서 포인터와 다릅니다.

당신은 참조의 주소를 취할 수없고 참조 된 주소 만 취할 수 있습니다. 참조가 생성 된 후에는 수정할 수 없습니다.

A T&와 a T*const( const포인터가 아닌 포인터에 적용됨)는 비교적 유사합니다. 실제 const의 주소를 가져 와서 수정하는 것은 참조를 수정 (직접 사용하는 모든 저장소)과 마찬가지로 정의되지 않은 동작입니다.

이제 실제로는 참조 저장소를 얻을 수 있습니다.

struct foo {
  int& x;
};

sizeof(foo)거의 확실하게 동일 sizeof(int*)합니다. 그러나 컴파일러는의 바이트에 직접 액세스하는 사람 foo이 실제로 참조 된 값을 변경할 수 있는 가능성을 무시할 수 있습니다. 이렇게하면 컴파일러가 참조 "포인터"구현을 한 번 읽은 다음 다시는 읽을 수 없습니다. struct foo{ int* x; }컴파일러가 있다면 *f.x포인터 값이 변경되지 않았다는 것을 매번 증명해야 할 것 입니다.

당신이 가졌다면 struct foo{ int*const x; }다시 불변성에서 참조처럼 행동하기 시작합니다 (선언 된 것을 수정하는 const것은 UB입니다).


내가 사용하는 컴파일러 작성자에 대해 알지 못하는 트릭은 참조 캡처를 람다로 압축하는 것입니다.

참조로 데이터를 캡처하는 람다가있는 경우 포인터를 통해 각 값을 캡처하는 대신 스택 프레임 포인터 만 캡처 할 수 있습니다. 각 지역 변수에 대한 오프셋은 스택 프레임 포인터의 컴파일 타임 상수입니다.

예외는 참조로 캡처 된 참조로, 참조 변수가 범위를 벗어나더라도 C ++에 대한 결함 보고서에서 유효한 상태로 유지되어야합니다. 그래서 그것들은 의사 포인터로 포착되어야합니다.

구체적인 예 (장난감 인 경우) :

void part( std::vector<int>& v, int left, int right ) {
  std::function<bool(int)> op = [&](int y){return y<left && y>right;};
  std::partition( begin(v), end(v), op );
}

the lambda above could capture only the stack frame pointer, and know where left and right are relative to it, reducing it size, instead of capturing two ints by (basically pointer) reference.

Here we have references implied by [&] whose existence is eliminated easier than if they where pointers captured by value:

void part( std::vector<int>& v, int left, int right ) {
  int* pleft=&left;
  int* pright=&right;
  std::function<bool(int)> op = [=](int y){return y<*pleft && y>*pright;};
  std::partition( begin(v), end(v), op );
}

There are a few other differences between references and pointers.

A reference can extend the lifetime of a temporary.

This is used heavily in for(:) loops. Both the definition of the for(:) loop relies on reference lifetime extension to avoid needless copies, and users of for(:) loops can use auto&& to automatically deduce the lightest weight way to wrap the iterated objects.

struct big { int data[1<<10]; };

std::array<big, 100> arr;

arr get_arr();

for (auto&& b : get_arr()) {
}

here reference lifetime extension carefully prevents needless copies from ever occuring. If we change make_arr to return a arr const& it continues to work without any copies. If we change get_arr to return a container that returns big elements by-value (say, an input iterator range), again no needless copies are done.

This is in a sense syntactic sugar, but it allows the same construct to be optimal in many cases without having to micro-optimize based on how things are returned or iterated over.


Similarly, forwarding references allow data to be treated as a const, non-const, lvalue or rvalue intelligently. Temporaries are marked as temporaries, data that users have no further need for is marked as temporary, data that will persist is marked as being an lvalue reference.

The advantage references have over non-references here is that you can form a rvalue reference to a temporary, and you cannot form a pointer to that temporary without passing it through an rvalue reference-to-lvalue reference conversion.


No


References are not just a syntactic difference; they also have different semantics:

  • A reference always aliases an existing object, unlike a pointer which may be nullptr (a sentinel value).
  • A reference cannot be re-seated, it always points to the same object throughout its lifetime.
  • A reference can extend the lifetime of an object, see binding to auto const& or auto&&.

Thus, at the language level, a reference is an entity of its own. The rest are implementation details.


There used to be efficiency advantages because references are easier for the compiler to optimize. However, modern compilers have gotten so good at it that there is no longer any advantage.

One huge advantage references have over pointers is that a reference can refer to a value in a register, while pointers can only point at blocks of memory. Take the address of something which would have been in a register, and you would force the compiler to put that value into a normal memory location instead. This can create tremendous benefits in tight loops.

However, modern compilers are so good that they now recognize a pointer that could have been a reference for all intents and purposes, and treat it exactly the same as if it were a reference. This can cause rather intriguing results in a debugger, where you can have a statement such as int* p = &x, ask the debugger to print the value of p, only to have it say something along the lines of "p cannot be printed" because x was actually in a register, and the compiler was treating *p as a reference to x! In this case, there literally is no value for p

(However, if you tried to do pointer arithmetic on p, you would then force the compiler to no longer optimize the pointer to act like a reference does, and everything would slow down)


8.3.2 References [dcl.ref]

A reference can be thought of as a name of an object

which is different from pointers which is a variable (unlike reference) that holds the address of a memory location of an Object**. The type of this variable is pointer to Object.

Internally Reference may be implemented as pointer, but standard never guaranteed so.

So to answer your question: C++Reference are not syntactic sugar to pointers. And whether it provides any speedup has already been answered in depth.

****** Object here it means any instance that has a memory address. Even pointers are Objects and so are functions (and thus we have nested pointers and function pointers). In similar sense, we do not have pointers to reference as they are not instantiated.

참고URL : https://stackoverflow.com/questions/31262942/c-references-are-they-just-syntactic-sugar

반응형