아래에 주어진 구조체 정의와 함께 ...
struct A {
virtual void hello() = 0;
};
접근법 # 1 :
struct B : public A {
virtual void hello() { ... }
};
접근법 # 2 :
struct B : public A {
void hello() { ... }
};
hello 함수를 재정의하는 두 가지 방법 사이에 차이점이 있습니까?
아래에 주어진 구조체 정의와 함께 ...
struct A {
virtual void hello() = 0;
};
접근법 # 1 :
struct B : public A {
virtual void hello() { ... }
};
접근법 # 2 :
struct B : public A {
void hello() { ... }
};
hello 함수를 재정의하는 두 가지 방법 사이에 차이점이 있습니까?
답변:
그들은 정확히 같습니다. 첫 번째 접근 방식은 더 많은 타이핑이 필요하고 잠재적으로 더 명확하다는 점 외에는 차이점이 없습니다.
함수의 '가상 성'은 암시 적으로 전파되지만 사용하는 적어도 하나의 컴파일러는 다음과 같은 경우 경고를 생성합니다. virtual
키워드를 명시 적으로 사용하지 않으면 하므로 컴파일러를 조용하게 유지하려는 경우에만 사용할 수 있습니다.
virtual
키워드를 포함한 순전히 문체 적 관점에서 그 기능이 가상이라는 사실을 사용자에게 명확하게 '광고'합니다. 이것은 A의 정의를 확인하지 않고도 B를 하위 클래스로 분류하는 모든 사람에게 중요합니다. 딥 클래스 계층의 경우 특히 중요합니다.
virtual
키워드는 파생 클래스에서 필요하지 않습니다. 다음은 C ++ Draft Standard (N3337) (강조 광산)의 지원 문서입니다.
10.3 가상 기능
2 가상 멤버 함수 경우
vf
클래스로 선언Base
하고, 클래스에Derived
직접 또는 간접적으로 유래의Base
멤버 함수,vf
동일한 이름의 파라미터 타입 목록 (8.3.5), CV-자격 및 REF - 규정 (Base::vf
선언 된 것과 동일하거나 존재하지 않는 경우)Derived::vf
가상 ( 선언되었는지 여부 )도 무시Base::vf
합니다.
아니요, virtual
파생 클래스의 가상 함수 재정 의 키워드는 필요하지 않습니다. 그러나 가상 함정을 재정의하지 못하는 관련 함정을 언급 할 가치가 있습니다.
재정 의 실패 는 파생 클래스에서 가상 함수를 재정의하려고하지만 서명에 오류가있어 새롭고 다른 가상 기능을 선언 할 경우 발생합니다. 이 함수는 기본 클래스 함수 의 오버로드 이거나 이름이 다를 수 있습니다. virtual
파생 클래스 함수 선언에서 키워드 를 사용하는지 여부에 관계없이 컴파일러는 기본 클래스에서 함수를 재정의하려는 의도를 알 수 없습니다.
그러나이 함정은 고맙게도 C ++ 11 명시 적 재정의 언어 기능 으로 해결되었으며 ,이를 통해 소스 코드는 멤버 함수가 기본 클래스 함수를 재정의하도록 의도되었음을 명확하게 지정할 수 있습니다.
struct Base {
virtual void some_func(float);
};
struct Derived : Base {
virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};
컴파일러는 컴파일 타임 오류를 발생시키고 프로그래밍 오류는 즉시 명백합니다 (아마도 Derived의 함수 float
가 인수로 사용 되어야 함 ).
WP : C ++ 11을 참조하십시오 .
템플릿이 있고 기본 클래스를 템플릿 매개 변수로 사용하기 시작하면 상당한 차이가 있습니다.
struct None {};
template<typename... Interfaces>
struct B : public Interfaces
{
void hello() { ... }
};
struct A {
virtual void hello() = 0;
};
template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
b.hello(); // indirect, non-virtual call
}
void hello(const A& a)
{
a.hello(); // Indirect virtual call, inlining is impossible in general
}
int main()
{
B<None> b; // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
B<None>* pb = &b;
B<None>& rb = b;
b.hello(); // direct call
pb->hello(); // pb-relative non-virtual call (1 redirection)
rb->hello(); // non-virtual call (1 redirection unless optimized out)
t_hello(b); // works as expected, one redirection
// hello(b); // compile-time error
B<A> ba; // Ok, vtable generated, sizeof(b) >= sizeof(void*)
B<None>* pba = &ba;
B<None>& rba = ba;
ba.hello(); // still can be a direct call, exact type of ba is deducible
pba->hello(); // pba-relative virtual call (usually 3 redirections)
rba->hello(); // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
//t_hello(b); // compile-time error (unless you add support for const A& in t_hello as well)
hello(ba);
}
그것의 재미있는 부분은 지금 인터페이스와 비 인터페이스 함수를 정의 할 수 있다는 것입니다 나중에 클래스를 정의 할 수 있습니다. 이는 라이브러리 간의 인터 워킹 인터페이스에 유용합니다 ( 단일 라이브러리 의 표준 설계 프로세스로 이것을 사용하지 마십시오 ). 모든 수업에 이것을 허용하는 데 비용이 들지 않습니다.typedef
. 원한다면 B .
이 작업을 수행하면 복사 / 이동 생성자를 템플릿으로 선언 할 수도 있습니다. 다른 인터페이스에서 구성 할 수 있으면 다른 B<>
유형 간에 '캐스트'할 수 있습니다.
그것은 당신에 대한 지원을 추가해야하는지 의문이다 const A&
에서 t_hello()
. 이 재 작성의 일반적인 이유는 주로 성능상의 이유로 상속 기반 전문화에서 템플릿 기반 전문화로 이동하지 않기 때문입니다. 이전 인터페이스를 계속 지원하면 이전 사용법을 거의 감지하지 못하거나 막을 수 없습니다.
virtual
키워드를 재정의 할 기본 클래스의 기능을 추가해야합니다. 귀하의 예 struct A
에서 기본 클래스입니다. virtual
파생 클래스에서 해당 함수를 사용하는 것은 아닙니다. 그러나 파생 클래스가 기본 클래스 자체가되기를 원하며 해당 함수를 재정의 할 수 있기를 원한다면 virtual
거기 에 배치해야 합니다.
struct B : public A {
virtual void hello() { ... }
};
struct C : public B {
void hello() { ... }
};
여기에서를 C
상속 받으 B
므로 B
기본 클래스가 아니며 파생 클래스이기도합니다 C
. 상속 다이어그램은 다음과 같습니다.
A
^
|
B
^
|
C
따라서 virtual
자식이있을 수있는 잠재적 인 기본 클래스 안에 함수 앞에를 배치해야합니다 . virtual
자녀가 귀하의 기능을 무시할 수 있습니다. virtual
파생 클래스 내부에서 함수 앞에 함수 를 배치하는 데 아무런 문제 가 없지만 필수는 아닙니다. 그러나 누군가 파생 클래스에서 상속하려면 메소드 재정의가 예상대로 작동하지 않는다는 것을 기쁘게 생각하지 않기 때문에 권장됩니다.
따라서 virtual
클래스에 기본 클래스의 기능을 재정의해야하는 자식이 없는지 확실하지 않으면 상속과 관련된 모든 클래스에서 함수 앞에 놓으십시오 . 좋은 습관입니다.
자식 클래스에 대한 Virtual 키워드를 확실히 포함시킬 것입니다.