수색…
통사론
가상 void f ();
가상 공극 g () = 0;
// C ++ 11 이상 :
- virtual void h () override;
- void i () override;
- 가상 void j () final;
- void k () final;
비고
- 정적이 아닌 비 템플릿 멤버 함수 만
virtual
될 수 있습니다. - 당신이 C ++ 11 이상을 사용하는 경우는 사용을 권장
override
기본 클래스에서 가상 멤버 함수를 오버라이드 (override) 할 때. - 다형 기본 클래스는 파생 된 객체를 기본 클래스에 대한 포인터를 통해 삭제할 수 있도록 가상 소멸자를 사용하는 경우가 많습니다 . 소멸자가 가상이 아닌 경우, 그러한 동작은 정의되지 않은 동작 [expr.delete] §5.3.5 / 3을 초래합니다.
C ++ 11 이상에서 가상으로 override 사용
지정자 override
는, C ++ 11 이후로, 함수의 시그 니챠의 마지막에 추가하는 경우, 특별한 의미를가집니다. 이것은 함수가
- 기본 클래스 &
- 기본 클래스 함수는
virtual
이 지시자의 run time
중요성은 컴파일러에 대한 지시 사항을 의미하지 않는다.
아래의 예는 오버라이드를 사용하지 않고 행동 변화를 보여줍니다.
override
없이 :
#include <iostream>
struct X {
virtual void f() { std::cout << "X::f()\n"; }
};
struct Y : X {
// Y::f() will not override X::f() because it has a different signature,
// but the compiler will accept the code (and silently ignore Y::f()).
virtual void f(int a) { std::cout << a << "\n"; }
};
override
:
#include <iostream>
struct X {
virtual void f() { std::cout << "X::f()\n"; }
};
struct Y : X {
// The compiler will alert you to the fact that Y::f() does not
// actually override anything.
virtual void f(int a) override { std::cout << a << "\n"; }
};
override
는 키워드가 아니라 함수 시그니처에만 나타날 수있는 특수 식별자입니다. 다른 모든 컨텍스트에서는 여전히 override
식별자로 사용할 수 있습니다.
void foo() {
int override = 1; // OK.
int virtual = 2; // Compilation error: keywords can't be used as identifiers.
}
가상 대 비가 상 회원 기능
가상 멤버 함수 사용 :
#include <iostream>
struct X {
virtual void f() { std::cout << "X::f()\n"; }
};
struct Y : X {
// Specifying virtual again here is optional
// because it can be inferred from X::f().
virtual void f() { std::cout << "Y::f()\n"; }
};
void call(X& a) {
a.f();
}
int main() {
X x;
Y y;
call(x); // outputs "X::f()"
call(y); // outputs "Y::f()"
}
가상 멤버 함수가없는 경우 :
#include <iostream>
struct X {
void f() { std::cout << "X::f()\n"; }
};
struct Y : X {
void f() { std::cout << "Y::f()\n"; }
};
void call(X& a) {
a.f();
}
int main() {
X x;
Y y;
call(x); // outputs "X::f()"
call(y); // outputs "X::f()"
}
최종 가상 함수
C ++ 11에서는 메서드 시그니처에 메서드가있는 경우 메서드 재정의를 금지하는 final
지정자가 도입되었습니다.
class Base {
public:
virtual void foo() {
std::cout << "Base::Foo\n";
}
};
class Derived1 : public Base {
public:
// Overriding Base::foo
void foo() final {
std::cout << "Derived1::Foo\n";
}
};
class Derived2 : public Derived1 {
public:
// Compilation error: cannot override final method
virtual void foo() {
std::cout << "Derived2::Foo\n";
}
};
지정자 final
은`virtual '멤버 함수에만 사용할 수 있으며 비 가상 멤버 함수에는 적용 할 수 없습니다
final
과 마찬가지로 파생 클래스에서 virtual
함수의 재정의를 방지하는 지정자 호출자 'override'도 있습니다.
지정자를 override
하고 final
함께 결합하여 원하는 효과를 얻을 수 있습니다.
class Derived1 : public Base {
public:
void foo() final override {
std::cout << "Derived1::Foo\n";
}
};
생성자와 소멸자의 가상 함수 동작
생성자와 소멸자의 가상 함수의 동작은 처음 만났을 때 종종 혼란 스럽습니다.
#include <iostream>
using namespace std;
class base {
public:
base() { f("base constructor"); }
~base() { f("base destructor"); }
virtual const char* v() { return "base::v()"; }
void f(const char* caller) {
cout << "When called from " << caller << ", " << v() << " gets called.\n";
}
};
class derived : public base {
public:
derived() { f("derived constructor"); }
~derived() { f("derived destructor"); }
const char* v() override { return "derived::v()"; }
};
int main() {
derived d;
}
산출:
기본 생성자에서 호출되면 base :: v ()가 호출됩니다.
파생 생성자에서 호출되면 derived :: v ()가 호출됩니다.
파생 된 소멸자에서 호출되면 derived :: v ()가 호출됩니다.
기본 소멸자에서 호출되면 base :: v ()가 호출됩니다.
그 이유는 파생 클래스가 아직 초기화되지 않았거나 (생성자의 경우) 또는 이미 소멸 된 경우 (소멸자의 경우) 추가 구성원을 정의 할 수 있으며 구성원 함수를 호출하는 것이 안전하지 않을 수 있기 때문입니다. 따라서 C ++ 객체의 생성 및 제거 중에 동적 유형 인 *this
는 생성자 또는 소멸자의 클래스로 간주되며 파생 클래스가 아닌 것으로 간주됩니다.
예:
#include <iostream>
#include <memory>
using namespace std;
class base {
public:
base()
{
std::cout << "foo is " << foo() << std::endl;
}
virtual int foo() { return 42; }
};
class derived : public base {
unique_ptr<int> ptr_;
public:
derived(int i) : ptr_(new int(i*i)) { }
// The following cannot be called before derived::derived due to how C++ behaves,
// if it was possible... Kaboom!
int foo() override { return *ptr_; }
};
int main() {
derived d(4);
}
순수 가상 함수
virtual
함수가 선언에 = 0
을 추가하여 순수 가상 (추상)임을 지정할 수도 있습니다. 하나 이상의 순수 가상 함수가있는 클래스는 추상 클래스로 간주되므로 인스턴스화 할 수 없습니다. 모든 순수 가상 함수를 정의하거나 상속 한 파생 클래스 만 인스턴스화 할 수 있습니다.
struct Abstract {
virtual void f() = 0;
};
struct Concrete {
void f() override {}
};
Abstract a; // Error.
Concrete c; // Good.
함수가 순수 가상으로 지정된 경우에도 기본 구현을 제공 할 수 있습니다. 그럼에도 불구하고이 함수는 여전히 추상화 된 것으로 간주되며 파생 클래스는 인스턴스화되기 전에 함수를 정의해야합니다. 이 경우 파생 클래스 '버전의 함수는 기본 클래스'버전을 호출 할 수 있습니다.
struct DefaultAbstract {
virtual void f() = 0;
};
void DefaultAbstract::f() {}
struct WhyWouldWeDoThis : DefaultAbstract {
void f() override { DefaultAbstract::f(); }
};
우리가 이것을 원할 수있는 몇 가지 이유가 있습니다 :
자체적으로 인스턴스화 할 수없는 클래스를 만들지 만 파생 클래스가 인스턴스화되는 것을 막지 않으면 소멸자를 순수 가상으로 선언 할 수 있습니다. 소멸자이기 때문에 인스턴스를 할당 해제 할 수 있도록하려면 반드시 정의해야합니다. 소멸자가 다형성 사용 중에 메모리 누수를 방지하기 위해 이미 가상 일 가능성이 높으므로 다른 함수
virtual
을 선언 할 때 불필요한 성능 저하가 발생하지는 않습니다. 이것은 인터페이스를 만들 때 유용 할 수 있습니다.struct Interface { virtual ~Interface() = 0; }; Interface::~Interface() = default; struct Implementation : Interface {}; // ~Implementation() is automatically defined by the compiler if not explicitly // specified, meeting the "must be defined before instantiation" requirement.
순수 가상 함수의 대부분 또는 모든 구현에 중복 코드가 포함되는 경우 해당 코드를 기본 클래스 버전으로 이동하여 코드를 더 쉽게 유지 관리 할 수 있습니다.
class SharedBase { State my_state; std::unique_ptr<Helper> my_helper; // ... public: virtual void config(const Context& cont) = 0; // ... }; /* virtual */ void SharedBase::config(const Context& cont) { my_helper = new Helper(my_state, cont.relevant_field); do_this(); and_that(); } class OneImplementation : public SharedBase { int i; // ... public: void config(const Context& cont) override; // ... }; void OneImplementation::config(const Context& cont) /* override */ { my_state = { cont.some_field, cont.another_field, i }; SharedBase::config(cont); my_unique_setup(); }; // And so on, for other classes derived from SharedBase.