operator &가 오버로드 될 때 어떻게 안정적으로 객체의 주소를 얻을 수 있습니까?


170

다음 프로그램을 고려하십시오.

struct ghost
{
    // ghosts like to pretend that they don't exist
    ghost* operator&() const volatile { return 0; }
};

int main()
{
    ghost clyde;
    ghost* clydes_address = &clyde; // darn; that's not clyde's address :'( 
}

clyde의 주소 는 어떻게 얻 습니까?

모든 유형의 객체에 똑같이 잘 작동하는 솔루션을 찾고 있습니다. C ++ 03 솔루션은 좋지만 C ++ 11 솔루션에도 관심이 있습니다. 가능하면 구현 별 동작을 피하십시오.

C ++ 11의 std::addressof함수 템플릿을 알고 있지만 여기에서 사용하는 데 관심이 없습니다. 표준 라이브러리 구현 자가이 함수 템플릿을 구현하는 방법을 이해하고 싶습니다.


41
@ jalf : 그 전략은 수용 가능하지만 이제는 개인에게 머리를 찔렀으므로 어떻게 가증 한 코드를 해결합니까? :-)
James McNellis 2018 년

5
음 @jalf, 때로는 필요한 이 연산자를 오버로드 및 프록시 개체를 반환 할 수 있습니다. 지금은 예를 생각할 수 없지만.
Konrad Rudolph

5
@ Konrad : 나도. 필요한 경우 연산자에 과부하가 걸리면 너무 많은 문제가 발생하기 때문에 더 나은 옵션으로 디자인을 다시 생각하는 것이 좋습니다. :)
jalf

2
@ Konrad : 약 20 년의 C ++ 프로그래밍에서 한 번 그 연산자에 과부하 를가 하려고했습니다. 그것은 그 20 년의 시작에있었습니다. 아, 그리고 나는 그것을 쓸모있게 만들지 못했습니다. 결과적으로, 운영자 과부하 FAQ 항목 은 "단일 주소의 운영자에게 과부하가 걸리지 않아야합니다."라고 말합니다. 다음에이 운영자에게 과부하가 걸리는 설득력있는 예를 제시하면 무료 맥주를 얻을 수 있습니다. (나는 당신이 베를린을 떠나는 것을 알고 있으므로 안전하게 제공 할 수 있습니다 :))
sbi

5
CComPtr<>CComQIPtr<>오버로드operator&
Simon Richter

답변:


102

업데이트 : C ++ 11에서는 std::addressof대신 사용할 수 있습니다 boost::addressof.


먼저 비트에서 컴파일러 작업을 빼고 Boost에서 코드를 복사 해 보겠습니다.

template<class T>
struct addr_impl_ref
{
  T & v_;

  inline addr_impl_ref( T & v ): v_( v ) {}
  inline operator T& () const { return v_; }

private:
  addr_impl_ref & operator=(const addr_impl_ref &);
};

template<class T>
struct addressof_impl
{
  static inline T * f( T & v, long ) {
    return reinterpret_cast<T*>(
        &const_cast<char&>(reinterpret_cast<const volatile char &>(v)));
  }

  static inline T * f( T * v, int ) { return v; }
};

template<class T>
T * addressof( T & v ) {
  return addressof_impl<T>::f( addr_impl_ref<T>( v ), 0 );
}

함수에 대한 참조를 전달하면 어떻게됩니까 ?

참고 : addressof함수에 대한 포인터와 함께 사용할 수 없습니다

C ++에서 if void func();가 선언되면 func인수를 사용하지 않고 결과를 반환하지 않는 함수에 대한 참조입니다. 기능이 참조는 소소 함수에 대한 포인터로 변환 할 수있다 -에서 @Konstantin: 13.3.3.2 양쪽에 따르면 T &T *기능과 구별된다. 첫 번째는 Identity 변환이고 두 번째는 "정확한 일치"순위 (13.3.3.1.1 표 9)를 갖는 함수-포인터 변환입니다.

함수 참조 통과 addr_impl_ref의 선택에 대한 과부하 해상도 모호성가, f더미 인수에 확실히 해결, 0이며, int제 1 및 승격 될 수있다 long(적분 변환)이.

따라서 우리는 단순히 포인터를 반환합니다.

변환 연산자를 사용하여 형식을 전달하면 어떻게됩니까?

변환 연산자가 a T*를 산출 하면 모호함이 있습니다. f(T&,long)두 번째 인수에는 Integral Promotion이 필요하지만 f(T*,int)변환 연산자는 첫 번째에 호출됩니다 (@litb 덕분)

때이다 addr_impl_ref. C ++ 표준의 의무의 차기 변환 순서가 가장 한 사용자 정의 변환에 포함 할 수있다. 형식을 래핑하고 addr_impl_ref변환 순서를 이미 사용함으로써 형식과 함께 제공되는 변환 연산자를 "비활성화"합니다.

따라서 f(T&,long)과부하가 선택됩니다 (및 Integral Promotion이 수행됨).

다른 유형은 어떻게됩니까?

따라서 f(T&,long)유형이 T*매개 변수 와 일치하지 않기 때문에 과부하가 선택 됩니다.

참고 : Borland 호환성과 관련하여 파일의 설명에서 배열은 포인터로 붕괴되지 않지만 참조로 전달됩니다.

이 과부하는 어떻게됩니까?

operator&오버로드되었을 수 있으므로 유형에 적용하지 않기를 원합니다 .

reinterpret_cast이 작업에 사용될 수있는 표준이 보장 됩니다 (@Matteo Italia의 답변 : 5.2.10 / 10 참조).

Boost는 컴파일러 경고를 피하기 위해 constvolatile한정자를 사용하여 멋진 기능을 추가합니다 (및 적절하게 사용 const_cast하여 제거).

  • 캐스트 T&char const volatile&
  • 스트립 constvolatile
  • &주소를 취하기 위해 연산자를 적용
  • 다시 캐스트 T*

const/ volatile저글링은 마술의 약간이지만 (오히려 4 과부하를 제공하는 대신) 작업을 단순화한다. 이후주의 T규정되지 않은, 우리가 통과하는 경우는 ghost const&, 다음 T*입니다 ghost const*따라서 예선 정말 손실되지 않았습니다.

편집 : 포인터 오버로드는 함수에 대한 포인터에 사용됩니다. 위의 설명을 약간 수정했습니다. 그래도 왜 필요한지 이해하지 못합니다 .

다음의 이데 오네 출력은 이것을 요약합니다.


2
"포인터를 전달하면 어떻게됩니까?" 부품이 잘못되었습니다. 어떤 타입 U에 포인터를 전달하면 'T'타입의 함수 address는 'U *'로 추론되고 addr_impl_ref는 'f (U * &, long)'과 'f (U **, int) ', 분명히 첫 번째 것이 선택됩니다.
Konstantin Oznobihin

@ Konstantin : 그렇습니다. f함수 템플릿 이있는 두 개의 과부하가있는 반면 템플릿 클래스의 정규 멤버 함수라고 지적했습니다. (이제 오버로드의 사용법, 팁이 무엇인지 알아야합니다.)
Matthieu M.

이것은 훌륭하고 잘 설명 된 답변입니다. 나는 단지 "cast through"보다 이것에 조금 더 많은 것이 있다고 생각했다 char*. 고마워요, Matthieu.
James McNellis

@ 제임스 : @Konstantin으로부터 많은 도움을 받았는데, 실수를 할 때마다 막대기로 머리를 때릴 것입니다. D
Matthieu M.

3
변환 기능이있는 유형을 해결해야하는 이유는 무엇입니까? 변환 함수를 호출하는 것보다 정확히 일치하는 것을 선호하지 T*않습니까? 편집 : 이제 알겠습니다. 그것은 0논쟁 의 여지가 있지만 십자형으로 끝나기 때문에 모호 할 것입니다.
Johannes Schaub-litb

99

사용하십시오 std::addressof.

배후에서 다음을 수행하는 것으로 생각할 수 있습니다.

  1. 객체를 참조에 대한 문자로 재 해석
  2. 그 주소를 취하십시오 (오버로드를 호출하지 마십시오)
  3. 포인터를 타입의 포인터로 다시 캐스트하십시오.

기존 구현 (Boost.Addressof 포함)은 추가 관리 constvolatile자격 을 갖추기 만하면 됩니다.


16
나는 쉽게 이해할 수 있기 때문에 선택된 것보다이 설명을 더 좋아한다.
Sled

49

트릭의 뒤에 boost::addressof@Luc 당통 제공하고 구현의 마법에 의존 reinterpret_cast; 표준은 §5.2.10 ¶10에서 명시 적으로

"pointer to " 유형의 표현식을 a를 사용하여 "pointer to " 유형으로 명시 적으로 변환 할 수있는 경우 유형의 lvalue 표현식을 T1"reference to T2" 유형으로 캐스트 할 T1수 있습니다 . 즉, 참조 캐스트 는 내장 및 연산자를 사용한 변환 과 동일한 효과를 갖습니다 . 결과는 소스 lvalue와 동일한 객체를 참조하지만 다른 유형을 갖는 lvalue입니다.T2reinterpret_castreinterpret_cast<T&>(x)*reinterpret_cast<T*>(&x)&*

이제 임의의 객체 참조를 char &(참조가 cv-qualified 인 경우 cv 규정을 사용하여)로 변환 할 수 있습니다. 포인터는 (cv-qualified)로 변환 할 수 있기 때문 char *입니다. 이제 우리는 char &객체를 오버로드하는 연산자가 더 이상 관련이 없으며 내장 &연산자로 주소를 얻을 수 있습니다 .

부스트 구현은 cv-qualified 객체로 작업하기위한 몇 가지 단계를 추가합니다. 첫 번째 reinterpret_cast작업은에 수행됩니다 const volatile char &. 그렇지 않으면 일반 char &캐스트가 작동하지 const않거나 volatile참조에 대해 작동 reinterpret_cast하지 않습니다 (제거 할 수 없음 const). 그런 다음 constvolatile로 제거되고 const_cast주소는 로 변경되고 "올바른"유형 &의 최종 reinterpet_cast이 완료됩니다.

const_cast을 제거 할 필요가 const/ volatile이는 const가 아닌 / 휘발성 참조에 추가되었습니다 수 있지만, 그것은 무엇 없습니다 "해"수행 const/의 volatile최종 있기 때문에, 처음에 참조 reinterpret_cast가 있다면 의지가 CV-자격을 다시 추가 처음에는 ( reinterpret_cast제거 const할 수 는 없지만 추가 할 수 있습니다).

의 나머지 코드는 addressof.hpp대부분이 해결 방법 인 것으로 보입니다. 는 static inline T * f( T * v, int )단지 볼랜드 컴파일러에 필요한 것 같다, 그러나 그것의 존재에 대한 필요성 소개하고 addr_impl_ref, 그렇지 않으면 포인터 타입이 두 번째 오버로드에 의해 잡힐 것을.

편집 : 다양한 과부하에는 다른 기능이 있습니다. @Matthieu M. 우수한 답변을 참조하십시오.

글쎄, 나는 더 이상 이것을 확신하지 못한다. 그 코드를 더 조사해야하지만 이제 저녁 식사를 요리하고 있습니다 :), 나중에 살펴 보겠습니다.


addressof에 포인터를 전달하는 것과 관련된 Matthieu M.의 설명이 올바르지 않습니다. 그런 편집으로 당신의 큰 대답을
망치지

"적절한 선호도", 추가 조사에 따르면 기능을 참조하기 위해 과부하가 필요합니다 void func(); boost::addressof(func);. 그러나이 왜 난 아직도 이해하지 않도록 코드를 컴파일과 같은 출력을 생성에서 GCC 4.3.4을 방지하지 않습니다 과부하를 제거 해야 이 오버로드를 할 수 있습니다.
Matthieu M.

@ Matthieu : gcc의 버그 인 것 같습니다. 13.3.3.2에 따르면 T와 T는 모두 기능을 구분할 수 없다. 첫 번째는 Identity 변환이고 두 번째는 "정확한 일치"순위 (13.3.3.1.1 표 9)를 갖는 함수-포인터 변환입니다. 따라서 추가 논쟁이 필요합니다.
Konstantin Oznobihin 2016 년

@ Matthieu : gcc 4.3.4 ( ideone.com/2f34P )로 시도하고 예상대로 모호합니다. 구현 주소 나 무료 함수 템플릿과 같은 오버로드 된 멤버 함수를 사용해 보셨습니까? 후자 ( ideone.com/vjCRs 등 )는 템플릿 인수 공제 규칙 (14.8.2.1/2)으로 인해 'T *'과부하가 선택됩니다.
Konstantin Oznobihin 2016 년

2
@ curiousguy : 왜 그렇게 생각하십니까? 나는 컴파일러가해야 할 일을 규정하는 특정 C ++ 표준 부품을 언급했으며 내가 액세스 한 모든 컴파일러 (gcc 4.3.4, comeau-online, VC6.0-VC2010을 포함하지만 이에 국한되지 않음)는 내가 설명한대로 모호성을보고합니다. 이 사건에 대한 당신의 추론을 자세히 설명해 주시겠습니까?
Konstantin Oznobihin

11

나는 이것을하는 구현을 보았다 addressof.

char* start = &reinterpret_cast<char&>(clyde);
ghost* pointer_to_clyde = reinterpret_cast<ghost*>(start);

이것이 어떻게 적합한 지 묻지 마십시오!


5
적법한. char*에일리어싱 규칙의 예외입니다.
강아지

6
@DeadMG 나는 이것이 적합하지 않다고 말하는 것이 아닙니다. 나는 당신이 나에게 묻지 말아야한다는 것을 말하는 것입니다 :)
Luc Danton

1
@DeadMG 여기에는 앨리어싱 문제가 없습니다. 문제는 reinterpret_cast<char*>잘 정의되어 있습니다.
curiousguy

2
@ curiousguy와 대답은 그렇습니다. 항상 모든 포인터 유형을 캐스팅 [unsigned] char *하여 지정된 객체의 객체 표현을 읽을 수 있습니다. 이것은 char특별한 특권 이있는 또 다른 영역 입니다.
underscore_d

@underscore_d 캐스트가 "항상 허용된다"고해서 캐스트 결과로 무엇이든 할 수있는 것은 아닙니다.
curiousguy

5

boost :: addressof 와 그 구현을 살펴보십시오 .


1
Boost 코드는 흥미롭지 만 기술이 어떻게 작동하는지 설명하지 않으며 두 개의 과부하가 필요한 이유도 설명하지 않습니다.
James McNellis

'정적 인라인 T * f (T * v, int)'과부하를 의미합니까? Borland C 해결 방법에만 필요한 것 같습니다. 사용 된 접근 방식은 매우 간단합니다. 'T &'를 'char &'로 변환하는 것은 미묘한 (비표준) 유일한 것입니다. 표준이지만 'T *'에서 'char *'로 캐스트 할 수 있지만 참조 캐스팅에 대한 요구 사항은없는 것 같습니다. 그럼에도 불구하고 대부분의 컴파일러에서 정확히 동일하게 작동 할 것으로 기대할 수 있습니다.
Konstantin Oznobihin 2016 년

@ Konstantin : 포인터의 addressof경우 포인터 자체를 반환 하기 때문에 과부하가 사용 됩니다. 사용자가 원하는 것인지 아닌지에 대해서는 논쟁의 여지가 있지만 그것이 지정된 방식입니다.
Matthieu M.

@Matthieu : 확실합니까? 내가 알 수있는 한, 모든 유형 (포인터 유형 포함)은에 싸여 addr_impl_ref있으므로 포인터 과부하를 절대로 호출해서는 안됩니다 ...
Matteo Italia

1
@KonstantinOznobihin이 질문에 대한 답은 아닙니다. 답변이 아닌 답을 어디에서 찾을 수 있는지에 대한 것 입니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.