code

C ++에서 참조를 다시 사용할 수없는 이유

codestyles 2020. 11. 7. 09:58
반응형

C ++에서 참조를 다시 사용할 수없는 이유


C ++ 참조에는 두 가지 속성이 있습니다.

  • 그들은 항상 같은 객체를 가리 킵니다.
  • 0 일 수 없습니다.

포인터는 그 반대입니다.

  • 그들은 다른 물체를 가리킬 수 있습니다.
  • 0이 될 수 있습니다.

C ++에 "nullable, reseatable reference 또는 pointer"가없는 이유는 무엇입니까? 참조를 다시 사용할 수없는 좋은 이유를 생각할 수 없습니다.

편집 : "연관"(여기서는 "참조"또는 "포인터"라는 단어는 사용하지 않음)이 유효하지 않은지 확인하고 싶을 때 일반적으로 참조를 사용하기 때문에 질문이 자주 발생합니다.

나는 "이 심판이 항상 동일한 객체를 참조한다는 점이 훌륭하다"고 생각한 적이 없다고 생각합니다. 참조를 다시 사용할 수있는 경우 다음과 같은 현재 동작을 계속 얻을 수 있습니다.

int i = 3;
int& const j = i;

이것은 이미 합법적 인 C ++이지만 의미가 없습니다.

나는 다음과 같이 내 질문을 다시 언급합니다. " '참조 객체입니다'디자인 의 근거는 무엇입니까 ? 참조 를 const로 선언 할 때만 아니라 항상 동일한 객체 로 만드는 것이 유용한 것으로 간주되는 이유는 무엇 입니까?"

건배, 펠릭스


C ++에서 참조 리 바인딩을 허용하지 않는 이유는 Stroustrup의 "Design and Evolution of C ++"에 나와 있습니다.

초기화 후에는 참조가 참조하는 내용을 변경할 수 없습니다. 즉, C ++ 참조가 초기화되면 나중에 다른 객체를 참조하도록 만들 수 없습니다. 다시 바인딩 할 수 없습니다. 나는 과거에 Algol68 참조에 물린 적이 있는데, 여기서 참조 된 객체를 r1=r2통해 r1할당하거나 유형에 따라 새 참조 값을 할당 r1(재 바인딩 r1) 할 수 r2있습니다. C ++에서 이러한 문제를 피하고 싶었습니다.


C ++에서는 종종 "참조 객체"라고합니다. 어떤 의미에서는 참입니다. 참조는 소스 코드가 컴파일 될 때 포인터로 처리되지만 참조는 함수가 호출 될 때 복사되지 않는 객체를 나타 내기위한 것입니다. 참조는 직접 주소를 지정할 수 없기 때문에 (예 : 참조에는 주소가없고 객체의 주소를 반환 함) 의미 상으로 다시 할당하는 것은 의미가 없습니다. 또한 C ++에는 재설정의 의미를 처리하는 포인터가 이미 있습니다.


왜냐하면 0이 될 수없는 reseatable 유형이 없기 때문입니다. 3 유형의 참조 / 포인터를 포함하지 않는 한. 약간의 이득을 얻기 위해 언어를 복잡하게 만드는 것은 무엇입니까? (그리고 4 번째 유형도 추가하지 않는 이유는 무엇입니까? 0이 될 수있는 재 등록 불가 참조?)

더 좋은 질문은 왜 참조를 다시 사용할 수 있도록하려는 것입니까? 그렇다면 많은 상황에서 유용성이 떨어질 것입니다. 컴파일러가 별칭 분석을 수행하기 어렵게 만듭니다.

Java 또는 C #의 참조가 재시동 가능한 주된 이유는 포인터 작업을 수행하기 때문입니다. 그들은 물체를 가리 킵니다. 객체의 별칭이 아닙니다.

다음의 효과는 무엇입니까?

int i = 42;
int& j = i;
j = 43;

오늘날의 C ++에서는 재 할당 할 수없는 참조를 사용하여 간단합니다. j는 i의 별칭이고 i는 43 값으로 끝납니다.

참조가 재 시착 가능한 경우 세 번째 줄은 참조 j를 다른 값에 바인딩합니다. 더 이상 별칭 i가 아니라 정수 리터럴 43 (물론 유효하지 않음)을 사용합니다. 또는 아마도 더 간단한 (또는 적어도 구문 적으로 유효한) 예 :

int i = 42;
int k = 43;
int& j = i;
j = k;

다시 사용할 수있는 참조 포함. j는이 코드를 평가 한 후 k를 가리 킵니다. C ++의 재 등록 불가 참조를 사용하면 j는 여전히 i를 가리키고 i에는 43이라는 값이 할당됩니다.

참조를 다시 사용할 수있게 만들면 언어의 의미가 변경됩니다. 참조는 더 이상 다른 변수의 별칭이 될 수 없습니다. 대신 자체 할당 연산자를 사용하여 별도의 값 유형이됩니다. 그리고 가장 일반적인 참조 사용 중 하나는 불가능합니다. 그리고 대가로 얻는 것은 아무것도 없습니다. 새로 얻은 참조 기능은 이미 포인터 형태로 존재했습니다. 이제 우리는 동일한 작업을 수행하는 두 가지 방법이 있고 현재 C ++ 언어의 참조가 수행하는 작업을 수행 할 방법이 없습니다.


참조는 포인터가 아니며 백그라운드에서 포인터로 구현 될 수 있지만 핵심 개념은 포인터와 동일하지 않습니다. 참조는 참조 *is*하는 객체 처럼보아야합니다 . 따라서 변경할 수 없으며 NULL 일 수 없습니다.

포인터는 단순히 메모리 주소를 보유하는 변수입니다. 포인터 자체에는 자체 메모리 주소가 있으며 해당 메모리 주소 내에는 가리키는 다른 메모리 주소 가 있습니다. 참조는 동일하지 않으며 자체 주소가 없으므로 다른 주소를 "보유"하도록 변경할 수 없습니다.

참조에 대한 parashift C ++ FAQ가 가장 적합 하다고 생각합니다 .

중요 참고 : 참조는 기본 어셈블리 언어의 주소를 사용하여 구현되는 경우가 많지만 참조를 개체에 대한 재미있는 포인터로 생각하지 마십시오. 참조는 객체입니다. 개체에 대한 포인터 나 개체의 복사본이 아닙니다. 대상입니다.

그리고 다시 FAQ 8.5에서 :

포인터와 달리 참조가 개체에 바인딩되면 다른 개체에 "재 장착"할 수 없습니다. 참조 자체는 객체가 아닙니다 (아이덴티티가 없습니다. 참조 주소를 사용하면 참조 대상의 주소를 알 수 있습니다. 참조는 참조 대상임을 기억하십시오).


reseatable 참조는 기능적으로 포인터와 동일합니다.

null 허용 여부 관련 : 컴파일 타임에 이러한 "재시동 가능한 참조"가 NULL이 아니라는 것을 보장 할 수 없으므로 이러한 테스트는 런타임에 수행해야합니다. 초기화되거나 NULL이 할당 될 때 예외를 발생시키는 스마트 포인터 스타일 클래스 템플릿을 작성하여 직접이 작업을 수행 할 수 있습니다.

struct null_pointer_exception { ... };

template<typename T>
struct non_null_pointer {
    // No default ctor as it could only sensibly produce a NULL pointer
    non_null_pointer(T* p) : _p(p) { die_if_null(); }
    non_null_pointer(non_null_pointer const& nnp) : _p(nnp._p) {}
    non_null_pointer& operator=(T* p) { _p = p; die_if_null(); }
    non_null_pointer& operator=(non_null_pointer const& nnp) { _p = nnp._p; }

    T& operator*() { return *_p; }
    T const& operator*() const { return *_p; }
    T* operator->() { return _p; }

    // Allow implicit conversion to T* for convenience
    operator T*() const { return _p; }

    // You also need to implement operators for +, -, +=, -=, ++, --

private:
    T* _p;
    void die_if_null() const {
        if (!_p) { throw null_pointer_exception(); }
    }
};

이것은 때때로 유용 할 수 있습니다. non_null_pointer<int>매개 변수를 취하는 함수는 함수가 취하는 것보다 확실히 더 많은 정보를 호출자에게 전달합니다 int*.


C ++ 참조 이름을 "별칭"으로 지정하는 것이 덜 혼란 스러웠 을까요? 다른 언급했듯이, C ++의 참조의 불구해야 으로 변수 가로가 아닌 포인터 /로 참조 참조 변수에. 따라서 재설정 가능 해야하는 좋은 이유를 생각할 수 없습니다 .

포인터를 다룰 때 종종 null을 값으로 허용하는 것이 합리적입니다 (그렇지 않으면 대신 참조를 원할 것입니다). 특별히 null을 허용하지 않으려면 항상 자체 스마트 포인터 유형을 코딩 할 수 있습니다.)


놀랍게도 여기에있는 많은 답변은 약간 모호하거나 요점 옆에 있습니다 (예 : 참조가 0이거나 유사 할 수 없기 때문이 아닙니다. 실제로 참조가 0 인 예제를 쉽게 구성 할 수 있기 때문이 아닙니다).

참조 재설정이 불가능한 실제 이유는 간단합니다.

  • 포인터를 사용하면 두 가지 작업을 수행 할 수 있습니다. ->또는 *연산자를 통해 포인터 뒤의 값 을 변경하고 포인터 자체를 변경 (직접 할당 =)합니다. 예:

    int a;
    int * p = &a;
    1. 값을 변경하려면 역 참조가 필요합니다. *p = 42;
    2. 포인터 변경 : p = 0;
  • 참조를 사용하면 값만 변경할 수 있습니다. 왜? 재설정을 표현하는 다른 구문이 없기 때문에. 예:

    int a = 10;
    int b = 20;
    int & r = a;
    r = b; // re-set r to b, or set a to 20?

즉, 참조를 재설정 할 수 있으면 모호합니다. 참조로 전달할 때 훨씬 더 의미가 있습니다.

void foo(int & r)
{
    int b = 20;
    r = b; // re-set r to a? or set a to 20?
}
void main()
{
    int a = 10;
    foo(a);
}

도움이되기를 바랍니다 :-)


C ++ 참조는 때때로 일부 컴파일러에서 강제로 0 될 수 있습니다 (그냥 그렇게하는 것은 나쁜 생각 *이며 표준 *을 위반 함).

int &x = *((int*)0); // Illegal but some compilers accept it

편집 : 나보다 표준을 훨씬 더 잘 아는 다양한 사람들에 따르면 위의 코드는 "정의되지 않은 동작"을 생성합니다. GCC 및 Visual Studio의 적어도 일부 버전에서는 이것이 예상되는 작업을 수행하는 것을 보았습니다. 포인터를 NULL로 설정하는 것과 동일합니다 (액세스 할 때 NULL 포인터 예외가 발생 함).


당신은 이것을 할 수 없습니다 :

int theInt = 0;
int& refToTheInt = theInt;

int otherInt = 42;
refToTheInt = otherInt;

... 같은 이유로 secondInt와 firstInt가 여기서 같은 값을 갖지 않습니다.

int firstInt = 1;
int secondInt = 2;
secondInt = firstInt;
firstInt = 3;

assert( firstInt != secondInt );

이것은 실제로 답은 아니지만이 제한에 대한 해결 방법입니다.

기본적으로 참조를 "리 바인드"하려고 할 때 실제로 다음 컨텍스트에서 새 값을 참조하기 위해 동일한 이름을 사용하려고합니다. C ++에서는 블록 범위를 도입하여이를 달성 할 수 있습니다.

jalf의 예에서

int i = 42;
int k = 43;
int& j = i;
//change i, or change j?
j = k;

i를 변경하려면 위와 같이 작성하십시오. 그러나 의미 j를 mean 로 변경하려면 다음 k과 같이 할 수 있습니다.

int i = 42;
int k = 43;
int& j = i;
//change i, or change j?
//change j!
{
    int& j = k;
    //do what ever with j's new meaning
}

최적화와 관련이 있다고 생각합니다.

정적 최적화는 변수가 의미하는 메모리 비트를 모호하지 않게 알 수있을 때 훨씬 쉽습니다. 포인터는이 조건을 깨고 다시 설정할 수있는 참조도 마찬가지입니다.


때때로 사물을 다시 가리킬 수 없어야하기 때문입니다. (예 : Singleton에 대한 참조)

인수가 null 일 수 없음을 아는 것은 함수에서 훌륭하기 때문입니다.

But mostly, because it allows use to have something that really is a pointer, but which acts like a local value object. C++ tries hard, to quote Stroustrup, to make class instances "do as the ints d". Passing an int by vaue is cheap, because an int fitss into a machine register. Classes are often bigger than ints, and passing them by value has significant overhead.

Being able to pass a pointer (which is often the size of an int, or maybe two ints) that "looks like" a value object allows us to write cleaner code, without the "implementation detail" of dereferences. And, along with operator overloading, it allows us to write classes use syntax similar to the syntax used with ints. In particular, it allows us to write template classes with syntax that can be equally applied to primitive, like ints, and classes (like a Complex number class).

And, with operator overloading especially, there are places were we should return an object, but again, it's much cheaper to return a pointer. Oncve again, returning a reference is our "out.

And pointers are hard. Not for you, maybe, and not to anyone that realizes a pointer is just the value of a memory address. But recalling my CS 101 class, they tripped up a number of students.

char* p = s; *p = *s; *p++ = *s++; i = ++*p;

can be confusing.

Heck, after 40 years of C, people still can't even agree if a pointer declaration should be:

char* p;

or

char *p;

I always wondered why they didn't make a reference assignment operator (say :=) for this.

Just to get on someone's nerves I wrote some code to change the target of a reference in a structure.

No, I do not recommend repeating my trick. It will break if ported to a sufficiently different architecture.


Being half serious: IMHO to make them little more different from pointers ;) You know that you can write:

MyClass & c = *new MyClass();

If you could also later write:

c = *new MyClass("other")

would it make sense to have any references alongside with pointers?

MyClass * a =  new MyClass();
MyClass & b = *new MyClass();
a =  new MyClass("other");
b = *new MyClass("another");

The fact that references in C++ are not nullable is a side-effect of them being just an alias.


I agree with the accepted answer. But for constness, they behave much like pointers though.

struct A{
    int y;
    int& x;
     A():y(0),x(y){}
};

int main(){
  A a;
  const A& ar=a;
  ar.x++;
}

works. See

Design reasons for the behavior of reference members of classes passed by const reference


There's a workaround if you want a member variable that's a reference and you want to be able to rebind it. While I find it useful and reliable, note that it uses some (very weak) assumptions on memory layout. It's up to you to decide whether it's within your coding standards.

#include <iostream>

struct Field_a_t
{
    int& a_;
    Field_a_t(int& a)
        : a_(a) {}
    Field_a_t& operator=(int& a)
    {
        // a_.~int(); // do this if you have a non-trivial destructor
        new(this)Field_a_t(a);
    }
};

struct MyType : Field_a_t
{
    char c_;
    MyType(int& a, char c)
        : Field_a_t(a)
        , c_(c) {}
};

int main()
{
    int i = 1;
    int j = 2;
    MyType x(i, 'x');
    std::cout << x.a_;
    x.a_ = 3;
    std::cout << i;
    ((Field_a_t&)x) = j;
    std::cout << x.a_;
    x.a_ = 4;
    std::cout << j;
}

This is not very efficient as you need a separate type for each reassignable reference field and make them base classes; also, there's a weak assumption here that a class having a single reference type won't have a __vfptr or any other type_id-related field that could potentially destroy runtime bindings of MyType. All the compilers I know satisfy that condition (and it would make little sense not doing so).

참고URL : https://stackoverflow.com/questions/728233/why-are-references-not-reseatable-in-c

반응형