C ++을 배우고 있으며 가상 기능을 사용하고 있습니다.
내가 읽은 것 (책과 온라인에서)에서 가상 함수는 파생 클래스에서 재정의 할 수있는 기본 클래스의 함수입니다.
그러나이 책의 앞부분에서 기본 상속에 대해 배울 때을 사용하지 않고 파생 클래스의 기본 함수를 재정의 할 수있었습니다 virtual
.
그래서 여기서 무엇을 놓치고 있습니까? 나는 가상 기능에 더 많은 것이 있다는 것을 알고 있으며 그것이 중요하다는 것을 분명히하고 싶습니다. 온라인에서 정답을 찾을 수 없습니다.
답변
다음은 virtual
함수가 무엇인지 뿐만 아니라 왜 필요한지 이해하는 방법입니다 .
이 두 클래스가 있다고 가정 해 봅시다.
class Animal
{
public:
void eat() { std::cout << "I'm eating generic food."; }
};
class Cat : public Animal
{
public:
void eat() { std::cout << "I'm eating a rat."; }
};
주요 기능에서 :
Animal *animal = new Animal;
Cat *cat = new Cat;
animal->eat(); // Outputs: "I'm eating generic food."
cat->eat(); // Outputs: "I'm eating a rat."
지금까지는 잘 되었습니까? 동물은 일반 음식을 먹고, 고양이는 쥐를 먹지 않고 모두 먹습니다 virtual
.
eat()
중간 함수 (이 예제에서는 사소한 함수)를 통해 호출 되도록 조금 변경해 보겠습니다 .
// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }
이제 우리의 주요 기능은 다음과 같습니다.
Animal *animal = new Animal;
Cat *cat = new Cat;
func(animal); // Outputs: "I'm eating generic food."
func(cat); // Outputs: "I'm eating generic food."
어 .. 우리는 고양이를 보냈는데 func()
쥐를 먹지 않아요. 당신은 과부하해야 func()
그것이 걸리는 그래서 Cat*
? 만약 당신이 동물로부터 더 많은 동물을 끌어 내야한다면 그들 모두는 그들 자신의 것이 필요합니다 func()
.
해결책은 클래스에서 가상 함수 를 만드는 것 eat()
입니다 Animal
.
class Animal
{
public:
virtual void eat() { std::cout << "I'm eating generic food."; }
};
class Cat : public Animal
{
public:
void eat() { std::cout << "I'm eating a rat."; }
};
본관:
func(animal); // Outputs: "I'm eating generic food."
func(cat); // Outputs: "I'm eating a rat."
끝난.
답변
“가상”이 없으면 “초기 바인딩”이됩니다. 사용되는 메소드의 구현은 호출하는 포인터의 유형에 따라 컴파일 타임에 결정됩니다.
“가상”을 사용하면 “늦은 바인딩”이 나타납니다. 사용되는 메소드 구현은 런타임시 지정된 오브젝트의 유형 (원래 구성한 내용)에 따라 결정됩니다. 이것은 반드시 해당 객체를 가리키는 포인터의 유형에 따라 생각할 필요는 없습니다.
class Base
{
public:
void Method1 () { std::cout << "Base::Method1" << std::endl; }
virtual void Method2 () { std::cout << "Base::Method2" << std::endl; }
};
class Derived : public Base
{
public:
void Method1 () { std::cout << "Derived::Method1" << std::endl; }
void Method2 () { std::cout << "Derived::Method2" << std::endl; }
};
Base* obj = new Derived ();
// Note - constructed as Derived, but pointer stored as Base*
obj->Method1 (); // Prints "Base::Method1"
obj->Method2 (); // Prints "Derived::Method2"
편집 – 이 질문을 참조하십시오 .
또한 이 튜토리얼 은 C ++에서의 초기 바인딩과 늦은 바인딩에 대해 다룹니다.
답변
이를 입증하려면 최소한 하나의 상속 레벨과 다운 캐스트가 필요합니다. 다음은 매우 간단한 예입니다.
class Animal
{
public:
// turn the following virtual modifier on/off to see what happens
//virtual
std::string Says() { return "?"; }
};
class Dog: public Animal
{
public: std::string Says() { return "Woof"; }
};
void test()
{
Dog* d = new Dog();
Animal* a = d; // refer to Dog instance with Animal pointer
std::cout << d->Says(); // always Woof
std::cout << a->Says(); // Woof or ?, depends on virtual
}
답변
안전한 다운 캐스팅 , 단순성 및 간결성을 위해 가상 방법이 필요합니다 .
가상 메소드는 간단하고 간결한 코드를 사용하여 안전하게 다운 캐스트하며, 더 복잡하고 자세한 코드에서 안전하지 않은 수동 캐스트를 피합니다.
비가 상 방법 ⇒ 정적 바인딩
다음 코드는 의도적으로 “잘못된”코드입니다. value
메소드를로 선언하지 않으므로 virtual
의도하지 않은 “잘못된”결과, 즉 0을 생성합니다.
#include <iostream>
using namespace std;
class Expression
{
public:
auto value() const
-> double
{ return 0.0; } // This should never be invoked, really.
};
class Number
: public Expression
{
private:
double number_;
public:
auto value() const
-> double
{ return number_; } // This is OK.
Number( double const number )
: Expression()
, number_( number )
{}
};
class Sum
: public Expression
{
private:
Expression const* a_;
Expression const* b_;
public:
auto value() const
-> double
{ return a_->value() + b_->value(); } // Uhm, bad! Very bad!
Sum( Expression const* const a, Expression const* const b )
: Expression()
, a_( a )
, b_( b )
{}
};
auto main() -> int
{
Number const a( 3.14 );
Number const b( 2.72 );
Number const c( 1.0 );
Sum const sum_ab( &a, &b );
Sum const sum( &sum_ab, &c );
cout << sum.value() << endl;
}
“bad”로 주석 처리 된 행에서는 정적으로 알려진 유형 (컴파일 시간에 알려진 유형)이 이고 메소드가 가상이 아니기 Expression::value
때문에 메소드가 호출 됩니다.Expression
value
가상 방법 ⇒ 동적 바인딩.
정적으로 알려진 유형 value
과 같이 선언 하면 각 호출이 실제 유형의 객체를 확인하고 해당 동적 유형 의 관련 구현을 호출합니다 .virtual
Expression
value
#include <iostream>
using namespace std;
class Expression
{
public:
virtual
auto value() const -> double
= 0;
};
class Number
: public Expression
{
private:
double number_;
public:
auto value() const -> double
override
{ return number_; }
Number( double const number )
: Expression()
, number_( number )
{}
};
class Sum
: public Expression
{
private:
Expression const* a_;
Expression const* b_;
public:
auto value() const -> double
override
{ return a_->value() + b_->value(); } // Dynamic binding, OK!
Sum( Expression const* const a, Expression const* const b )
: Expression()
, a_( a )
, b_( b )
{}
};
auto main() -> int
{
Number const a( 3.14 );
Number const b( 2.72 );
Number const c( 1.0 );
Sum const sum_ab( &a, &b );
Sum const sum( &sum_ab, &c );
cout << sum.value() << endl;
}
여기서는 6.86
가상 메소드가 사실상 호출 되기 때문에 출력은 그대로 있어야합니다 . 이를 호출의 동적 바인딩 이라고도 합니다. 실제 동적 유형의 오브젝트를 찾아서 약간의 점검이 수행되고 해당 동적 유형에 대한 관련 메소드 구현이 호출됩니다.
관련 구현은 가장 구체적인 (가장 파생 된) 클래스의 구현입니다.
여기서 파생 클래스의 메소드 구현은 표시되지 virtual
않지만 대신 표시 override
됩니다. 표시 할 수는 virtual
있지만 자동으로 가상입니다. override
가있는 경우 해당 키워드 보장하지만 하지 몇 가지 기본 클래스에서 이러한 가상 메서드, 다음 (바람직하다) 오류가 발생합니다.
가상 방법없이이 작업을 수행 할 때의 추악함
없이 virtual
하나는 일부 구현해야 할 일이 자신 동적 바인딩의 버전을. 일반적으로 안전하지 않은 수동 다운 캐스팅, 복잡성 및 세부 정보가 포함됩니다.
단일 함수의 경우 여기에서와 같이 객체에 함수 포인터를 저장하고 해당 함수 포인터를 통해 호출하면 충분하지만 안전하지 않은 다운 캐스트, 복잡성 및 세부 정보가 포함됩니다.
#include <iostream>
using namespace std;
class Expression
{
protected:
typedef auto Value_func( Expression const* ) -> double;
Value_func* value_func_;
public:
auto value() const
-> double
{ return value_func_( this ); }
Expression(): value_func_( nullptr ) {} // Like a pure virtual.
};
class Number
: public Expression
{
private:
double number_;
static
auto specific_value_func( Expression const* expr )
-> double
{ return static_cast<Number const*>( expr )->number_; }
public:
Number( double const number )
: Expression()
, number_( number )
{ value_func_ = &Number::specific_value_func; }
};
class Sum
: public Expression
{
private:
Expression const* a_;
Expression const* b_;
static
auto specific_value_func( Expression const* expr )
-> double
{
auto const p_self = static_cast<Sum const*>( expr );
return p_self->a_->value() + p_self->b_->value();
}
public:
Sum( Expression const* const a, Expression const* const b )
: Expression()
, a_( a )
, b_( b )
{ value_func_ = &Sum::specific_value_func; }
};
auto main() -> int
{
Number const a( 3.14 );
Number const b( 2.72 );
Number const c( 1.0 );
Sum const sum_ab( &a, &b );
Sum const sum( &sum_ab, &c );
cout << sum.value() << endl;
}
이를 보는 한 가지 긍정적 인 방법은 위와 같이 안전하지 않은 다운 캐스팅, 복잡성 및 자세한 정보가 표시되는 경우 가상 방법이 실제로 도움이 될 수 있습니다.
답변
가상 함수는 런타임 다형성 을 지원하는 데 사용됩니다 .
즉, virtual 키워드는 컴파일러에게 컴파일 타임에 함수 바인딩 결정을하지 말고 런타임에 연기하도록 지시합니다 . “
-
virtual
기본 클래스 선언에서 키워드 앞에있는 함수를 가상으로 만들 수 있습니다 . 예를 들어class Base { virtual void func(); }
-
때 기본 클래스는 가상 멤버 함수를 가지고, 모든 클래스 기본 클래스에서 상속을 할 수있는 재정 과 기능을 정확히 같은 프로토 타입 , 즉 유일한 기능, 재정 기능이 아닌 인터페이스 할 수있다.
class Derive : public Base { void func(); }
-
기본 클래스 포인터는 파생 클래스 개체뿐만 아니라 기본 클래스 개체를 가리키는 데 사용할 수 있습니다.
- Base 클래스 포인터를 사용하여 가상 함수를 호출하면 컴파일러는 런타임에 함수의 어떤 버전 (즉, Base 클래스 버전 또는 재정의 된 파생 클래스 버전)을 호출할지 결정합니다. 이것을 런타임 다형성 이라고 합니다.
답변
기본 클래스가 Base
이고 파생 클래스가 Der
이면 Base *p
실제로 인스턴스를 가리키는 포인터를 가질 수 있습니다 Der
. 당신이 호출 할 때 p->foo();
경우 foo
입니다 하지 가상, 다음 Base
그것의의 버전은 그 사실을 무시하고, 실행 p
실제로 포인트를 Der
. foo 가 virtual 인 경우 p->foo()
, “leafmost”재정의를 실행 foo
하고 지정된 항목의 실제 클래스를 완전히 고려합니다. 따라서 가상과 비가상의 차이점은 실제로 매우 중요합니다. 전자는 OO 프로그래밍의 핵심 개념 인 런타임 다형성을 허용 하지만 후자는 그렇지 않습니다.
답변
가상 기능의 필요성 설명 [쉽게 이해]
#include<iostream>
using namespace std;
class A{
public:
void show(){
cout << " Hello from Class A";
}
};
class B :public A{
public:
void show(){
cout << " Hello from Class B";
}
};
int main(){
A *a1 = new B; // Create a base class pointer and assign address of derived object.
a1->show();
}
출력은 다음과 같습니다.
Hello from Class A.
그러나 가상 기능 :
#include<iostream>
using namespace std;
class A{
public:
virtual void show(){
cout << " Hello from Class A";
}
};
class B :public A{
public:
virtual void show(){
cout << " Hello from Class B";
}
};
int main(){
A *a1 = new B;
a1->show();
}
출력은 다음과 같습니다.
Hello from Class B.
따라서 가상 기능을 사용하면 런타임 다형성을 달성 할 수 있습니다.