가상 할당이 동일한 서명의 다른 가상 함수와 다르게 행동합니까?
-
13-09-2019 - |
문제
가상 할당 연산자를 구현하는 동안 나는 재미있는 행동으로 끝났습니다. G ++ 4.1, 4.3 및 vs 2005가 동일한 동작을 공유하기 때문에 컴파일러 글리치가 아닙니다.
기본적으로 가상 연산자 =는 실제로 실행중인 코드와 관련하여 다른 가상 기능과 다르게 행동합니다.
struct Base {
virtual Base& f( Base const & ) {
std::cout << "Base::f(Base const &)" << std::endl;
return *this;
}
virtual Base& operator=( Base const & ) {
std::cout << "Base::operator=(Base const &)" << std::endl;
return *this;
}
};
struct Derived : public Base {
virtual Base& f( Base const & ) {
std::cout << "Derived::f(Base const &)" << std::endl;
return *this;
}
virtual Base& operator=( Base const & ) {
std::cout << "Derived::operator=( Base const & )" << std::endl;
return *this;
}
};
int main() {
Derived a, b;
a.f( b ); // [0] outputs: Derived::f(Base const &) (expected result)
a = b; // [1] outputs: Base::operator=(Base const &)
Base & ba = a;
Base & bb = b;
ba = bb; // [2] outputs: Derived::operator=(Base const &)
Derived & da = a;
Derived & db = b;
da = db; // [3] outputs: Base::operator=(Base const &)
ba = da; // [4] outputs: Derived::operator=(Base const &)
da = ba; // [5] outputs: Derived::operator=(Base const &)
}
효과는 가상 연산자 =가 실제 파생 된 객체 ([1]을 통해 호출 될 때 연산자의 기본 버전을 호출하여 [1]과 비교하여 동일한 서명)의 다른 가상 함수와 다른 동작을 가지고 있다는 것입니다. ) 또는 파생 된 참조 ([3]) 기본 참조 ([2])를 통해 호출 될 때 또는 lvalue 또는 rvalue가 기본 참조이고 다른 하나는 파생 된 참조 ([4], [5]).
이 이상한 행동에 대한 현명한 설명이 있습니까?
해결책
어떻게 진행되는지는 다음과 같습니다.
내가 [1]로 바꾸면
a = *((Base*)&b);
그런 다음 상황이 당신이 기대하는 방식으로 작동합니다. 자동으로 생성 된 할당 연산자가 있습니다 Derived
다음과 같이 보입니다.
Derived& operator=(Derived const & that) {
Base::operator=(that);
// rewrite all Derived members by using their assignment operator, for example
foo = that.foo;
bar = that.bar;
return *this;
}
당신의 예에서 컴파일러는 그것을 추측하기에 충분한 정보가 있습니다 a
그리고 b
유형입니다 Derived
따라서 위의 자동으로 생성 된 연산자를 사용하여 귀하를 호출합니다. 그것이 당신이 얻은 방법입니다 [1]. 내 포인터 캐스팅은 컴파일러가 당신의 방식으로 그렇게하도록 강요합니다. b
유형입니다 Derived
그리고 그것은 사용합니다 Base
.
다른 결과는 같은 방식으로 설명 될 수 있습니다.
다른 팁
이 경우 세 가지 연산자가 있습니다.
Base::operator=(Base const&) // virtual
Derived::operator=(Base const&) // virtual
Derived::operator=(Derived const&) // Compiler generated, calls Base::operator=(Base const&) directly
이것은 왜 그것이 base :: operator = (Base const &)처럼 보이는 이유를 설명합니다. [1]. 컴파일러 생성 버전에서 호출됩니다. 케이스에도 동일하게 적용됩니다 [3]. 사례 2에서 오른쪽 인수 'BB'에는 유형 기반이 있으므로 파생 된 :: OPERATOR = (파생)를 호출 할 수 없습니다.
파생 클래스에 정의 된 사용자 제공 할당 연산자는 없습니다. 따라서 컴파일러는 하나의 합성 및 내부적으로 기본 클래스 할당 연산자가 파생 클래스를 위해 합성 할당 연산자로부터 호출됩니다.
virtual Base& operator=( Base const & ) //is not assignment operator for Derived
따라서, a = b; // [1] outputs: Base::operator=(Base const &)
파생 클래스에서 기본 클래스 할당 연산자가 무시되었으므로 재정의 메소드는 파생 클래스의 가상 테이블에서 항목을 가져옵니다. 메소드가 참조 또는 포인터를 통해 호출되면 도출 된 클래스 재정의 메소드는 런타임에 VTable 입력 해상도로 인해 호출됩니다.
ba = bb; // [2] outputs: Derived::operator=(Base const &)
==> 내부 ==> (Object-> vtable [할당 연산자]) 객체가 속한 클래스의 Vtable에서 할당 연산자의 항목을 가져 와서 메소드를 호출하십시오.
적절한 것을 제공하지 못하는 경우 operator=
(즉, 올바른 반환 및 인수 유형), 기본값 operator=
사용자 정의 된 제품을 과부하시키는 컴파일러에서 제공합니다. 귀하의 경우에는 호출 할 것입니다 Base::operator= (Base const& )
파생 된 멤버를 복사하기 전에.
이것을 확인하십시오 링크 연산자에 대한 자세한 내용은 가상으로 만들어졌습니다.
그 이유는 컴파일러가 기본 할당을 제공 한 이유입니다 operator=
. 시나리오에서 호출됩니다 a = b
그리고 우리가 알듯이, 기본값은 내부적으로 기본 할당 연산자를 호출합니다.
가상 할당에 대한 자세한 설명은 다음에서 찾을 수 있습니다. https://stackoverflow.com/a/26906275/3235055