2010-12-09 3 views
7

멤버 포인터에 대해 살펴본 대부분의 설명은 멤버가 속한 형식에서 허용되는 변환에 초점을 둡니다. 내 질문은 회원의 유형에 대한 전환에 관한 것입니다. - 여기 왜 차이 * *베이스에하는 것은 일반적으로 허용되는 파생에서데이터 멤버에 대한 포인터의 비 암시 적 변환과 비 멤버의 암시 적 변환

// error C2440: 'initializing' : cannot convert from 'Derived Foo::* ' to 'Base Foo::* ' 
Base Foo::*p = &Foo::m_Derived; 

변환 : 이러한 선언을 감안할 때

struct Base{}; 
struct Derived : public Base{}; 
struct Foo{ Derived m_Derived; }; 

, 다음 코드는 오류 (2008 MSVC)를 생산?

+1

"기본 *에서 파생 * 로의 변환은 일반적으로 허용됩니다." 나는 당신이 이것을 뒤로 가지고 있다고 생각합니다. –

+0

잘 잡으세요. 결정된. – Chris

+0

http://stackoverflow.com/questions/4295117/pointer-to-member-conversion을 참조하십시오. – icecrime

답변

0

흥미로운 질문입니다. 데이터 포인터는 거의 사용되지 않아 규칙에 익숙하지 않습니다.

그러나 다중 상속 때문에 돈을 지불 할 것입니다. Base와 Derived가 가상 상속을 사용하면 컴파일러는 주어진 Derived 내에서 Base의 오프셋을 컴파일 타임에 알 수 없으므로 컴파일 타임 오프셋을 불가능하게하며 비 가상의 경우 합법화하기에는 너무 많은 작업이 필요합니다 계승.

+0

하지만 동일한 인수가 비회원 포인터에 대한 변환을 허용하는 것에 반대하여 작동하지만, 그렇지? – lijie

+0

@lijie : 아니요, 이러한 변환은 런타임에 발생하기 때문입니다. 이 변환은 컴파일 타임에 효과적으로 발생합니다. – Puppy

+0

"이 전환"이란 무엇입니까? – lijie

2

분산이 뒤로 있습니다.

반환 형식은 암시 적으로 기본 형식 (반 변형)으로 변환됩니다. 그러나 매개 변수는 암시 적으로 파생 된 유형 (공분산)으로 변환되며, 포인터가있는 멤버의 클래스 유형은 매개 변수으로 작동합니다. 이를 확인하기 위해 Liskov 대체 원리를 적용 해 봅시다 : Base* 계약은 : * 연산자를 사용할 때 "나는 당신에게 기지를 줄"입니다. Derived*의 계약은 "나는 당신에게 파생물을 주겠다.

분명히 Derived*Base* 대신 사용될 수 있습니다. 따라서 Derived*에서 Base*으로의 암시 적 변환이 있습니다.

그러나 회원 포인터에 대한 계약을 고려하십시오.

int Base::*의 계약은 "나에게 자료를주고 내가 INT에게 당신을 줄 것이다"(A 파생은 기본, 그래서 사람들이 너무 좋아하다) 을 int Derived::*의 계약은 "나에게 줘 파생 및 나는 "int를 당신을 다시 줄 것이다 (그러나하지 않습니다 오래된 Base, 그것은 Derived해야합니다)

당신이 Derived 아닌 Base을 가지고 상상해보십시오. int Base::*을 역 참조 할 때 제대로 작동하지만 int Derived*과 함께 사용할 수는 없습니다.

그러나 Derived 사용자는 int Base::*int Derived::*을 모두 역 참조 할 수 있습니다. 따라서 int Derived::*

아아에 int Base::*에서 암시 적 변환이, 나는 당신이 말한과 회원이 속한 유형을 분석 무엇을했다.

LSP는 여전히 작동합니다. 그리고 적어도 유형 안전에 따라 전환이 합법적이어야한다는 것에 동의합니다.계약서는 "내게 Foo을 줘. 나는 너에게 Derived"을 줄 것이다. 분명히 암시 적 변환으로 구성하여 Foo에서 Base까지 얻을 수있다. 그래서 안전합니다. DeadMG는 기본 하위 객체의 관계 위치, 특히 가상 상속의 잠재적 인 복잡성을 지적하는 올바른 방법 일 것입니다. 그러나 멤버 간 포인터는 참조 연산자의 LHS에서 이러한 문제를 처리하므로 결과도 마찬가지입니다.

최종 답변은 아마도 표준에서 변환이 합법적 일 것을 요구하지 않는다는 것입니다.

+0

이상하게도'Derived * (Foo :: *)'_can_를'Base * (Foo :: *)'(코모)로 변환 할 수 있습니다. 이것이 "확장"인지 궁금합니다. – lijie

1

@Ben Voigt : 왜 정답을 쳤습니까?

안전 포인터 변환은 반대 변형입니다. 즉, 안전하게 업신 브 루프 할 수는 있지만 다운 캐스트는 허용 할 수 없습니다.

회원 전환에 안전한 포인터는 공동 변종이므로 안전하게 다운 캐스트 할 수는 있지만 업 캐스팅은 할 수 없습니다.

이유는 생각할 때 쉽게 알 수 있습니다. Base의 멤버에 대한 포인터를 가지고 있다고 가정 해 봅시다. Base에 대한 오프셋입니다. 그런 다음 완전한 객체가 파생 된 경우 파생 된 객체에 대한 동일한 멤버의 오프셋은 무엇입니까? 일반적으로 정확히 같은 오프셋 : Base와 Derived에 대한 포인터가 같은 주소 일 경우 확실히됩니다. 다중 상속과 가상베이스가있는 경우 다소 번잡합니다.

실제로 OP의 예제 코드는 업 캐스트가 안전하지 않은 이유의 완벽한 예입니다. 파생 클래스 멤버의 오프셋을 다음에 적용 할 수 있습니다. 어떤 Base를 가리키고 Base subobject의 끝 부분을 가리 키십시오. 실제로는 Derived가 아닌 Derived2 인 경우에만 OK입니다.

+1

하지만 우리는 데이터 포인터의 클래스를 취소하려고하지 않습니다. 우리는 데이터 포인터가 해당 유형의 업 캐스팅을 가리 키려고합니다 (이름이 부족합니다 ...). 이것들은 같은 것이 아닙니다. – Hexagon

+0

포인터가 단순한 오프셋이어야하고, OP의 질문이 "기본 멤버에 대한 포인터"에 관한 것이 아니라는 것은 아무것도 없습니다. 이것은 멤버 타입이 Base 인 "Foo 멤버에 대한 포인터"에 관한 것입니다. 그리고 컴파일러는 "A의 멤버에 대한 포인터"를 "B의 멤버에 대한 포인터"로 변환하는 것을 고려합니다. – lijie

+0

당신의 원래 답변은 '파생 된'멤버에 대한 이야기이기 때문에 'Base'의 멤버들.그러나 질문은 'Base'또는 'Derived'라고 입력 한 멤버에 관한 것이지만 모두 ** Foo의 멤버입니다. –