C ++ 가상 함수 반환 유형


답변:


86

경우에 따라 예, 반환 유형이 원래 반환 유형과 공변 하는 한 파생 클래스가 다른 반환 유형을 사용하여 가상 함수를 재정의하는 것이 합법적입니다 . 예를 들어 다음을 고려하십시오.

class Base {
public:
    virtual ~Base() {}

    virtual Base* clone() const = 0;
};

class Derived: public Base {
public:
    virtual Derived* clone() const {
        return new Derived(*this);
    }
};

여기에서 를 반환하는 Base라는 순수 가상 함수를 정의 clone합니다 Base *. 파생 된 구현에서이 가상 함수는 반환 형식을 사용하여 재정의됩니다 Derived *. 반환 유형은 기본과 동일하지 않지만 언제든지 작성할 수 있기 때문에 완벽하게 안전합니다.

Base* ptr = /* ... */
Base* clone = ptr->clone();

에 대한 호출 clone()은 항상 Base객체에 대한 포인터를 반환합니다.를 반환 Derived*하더라도이 포인터는 암시 적으로 a로 변환 가능 Base*하고 작업이 잘 정의되어 있기 때문입니다.

보다 일반적으로 함수의 반환 유형은 서명의 일부로 간주되지 않습니다. 반환 유형이 공변이면 모든 반환 유형으로 멤버 함수를 재정의 할 수 있습니다.


9
"반환 유형을 사용하여 멤버 함수를 재정의 할 수 있습니다."는 올바르지 않습니다. 반환 유형이 동일하거나 공변 (설명한대로) 인 기간을 재정의 할 수 있습니다. 여기에는 더 이상 일반적인 경우가 없습니다.
bronekk

1
@ bronekk- 당신이 인용 한 나머지 문장은 새로운 반환 유형은 원래 유형이있는 곳이면 어디에서나 사용할 수 있어야한다고 말합니다. 즉, 새 유형은 원본과 공변합니다.
templatetypedef

나머지 문장은 정확하지 않습니다. 대체 상상 Base*longDerived*함께 int(또는 다른 방법의 주위에, 문제가되지 않습니다). 작동하지 않습니다.
bronekk

@ bronekk- 아 그래, 나는 그것을 생각하지 않았다! 지적 해 주셔서 감사합니다.
templatetypedef

1
유형은 원래 유형이있는 모든 곳에서 사용할 수 있으며 공분산은 두 가지 다른 컨텍스트입니다. 공변 이란 함수를 구현하는 유형 간의 관계와 반환 된 유형 간의 관계가 같은 방식으로 다르다는 것을 의미합니다. Contravariant (C ++에서는 유용하지 않지만)는 반대의 맥락입니다. 일부 언어는 동적 디스패치에서 반 변성 인수를 허용합니다 (기본이 T 유형의 객체를 사용하는 경우 파생 된 유형은 T '를 취할 수 있으며 여기서 T'는 T의 기본입니다-한 계층 아래로 내려 가면 다른 계층으로 이동) ).
David Rodríguez-dribeas

53

예. 반환 유형은 공변 인 한 다를 수 있습니다 . C ++ 표준은이를 다음과 같이 설명합니다 (§10.3 / 5).

재정의 함수의 반환 유형은 재정의 된 함수의 반환 유형과 동일하거나 함수 클래스와 공변 해야합니다. 함수가 함수를 D::f재정의하는 B::f경우 다음 기준을 충족하면 함수의 반환 유형이 공변합니다.

  • 둘 다 클래스에 대한 포인터 또는 클래스 98에 대한 참조 )
  • 의 반환 유형에 B::f있는 클래스는 D::f또는 의 반환 유형에있는 클래스와 동일한 클래스이고,의 반환 유형에있는 클래스 의 명확한 직접 또는 간접 기본 클래스이고에서 D::f액세스 할 수 있습니다.D
  • 포인터 또는 참조는 모두 동일한 cv-qualification을 D::f가지며의 반환 유형에있는 클래스 유형은의 반환 유형에있는 클래스 유형과 같거나 cv-qualification이 적거나 cv-qualification이 적습니다 B::f.

각주 98은 "클래스에 대한 다중 레벨 포인터 또는 클래스에 대한 다중 레벨 포인터에 대한 참조는 허용되지 않습니다."라고 지적합니다.

요컨대, if D가의 하위 B유형 인 경우 함수 D의 반환 유형은에서 함수의 반환 유형의 하위 유형이어야합니다 B. 반환 유형에 따라 자신을 때 가장 일반적인 예는 D하고 B있지만 될 필요가 없습니다. 두 개의 개별 유형 계층이있는 경우를 고려하십시오.

struct Base { /* ... */ };
struct Derived: public Base { /* ... */ };

struct B {
  virtual Base* func() { return new Base; }
  virtual ~B() { }
};
struct D: public B {
  Derived* func() { return new Derived; }
};

int main() {
  B* b = new D;
  Base* base = b->func();
  delete base;
  delete b;
}

이것이 작동하는 이유는 호출자 funcBase포인터를 기대 하기 때문 입니다. 모든 Base포인터가 작동합니다. 따라서 D::func항상 Derived포인터를 반환하겠다고 약속하면 모든 Derived포인터가 암시 적으로 Base포인터 로 변환 될 수 있기 때문에 항상 조상 클래스가 배치 한 계약을 충족합니다 . 따라서 발신자는 항상 기대하는 것을 얻을 수 있습니다.


반환 유형을 변경하는 것 외에도 일부 언어에서는 재정의 함수 의 매개 변수 유형 도 변경할 수 있습니다. 그렇게 할 때 일반적으로 반 변성이 필요합니다 . 경우 즉, B::f을 받아 Derived*, 다음 D::fa를 허용 할 것이다 Base*. 후손들은 그들이 받아들이는 것에 대해 더 느슨해 지고 그들이 반환하는 것에 더 엄격 해질 수 있습니다 . C ++에서는 매개 변수 유형 반공 변성을 허용하지 않습니다. 매개 변수 유형을 변경하면 C ++는이를 새로운 함수로 간주하므로 오버로딩 및 숨기기가 시작됩니다. 이 주제에 대한 자세한 내용 은 Wikipedia의 공분산 및 반공 분산 (컴퓨터 과학) 을 참조하십시오 .


2
이것은 실제 기능입니까 아니면 해결에 사용되지 않는 반환 유형의 부작용입니까?
Martin York

1
@Martin, 확실히 기능입니다. 나는 과부하 해결이 그것과 아무 관련이 없다고 확신합니다. 함수를 재정의하는 경우 반환 유형 사용됩니다.
Rob Kennedy

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.