为什么我们需要 C++ 中的虚函数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2391679/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why do we need virtual functions in C++?
提问by Jake Wilson
I'm learning C++ and I'm just getting into virtual functions.
我正在学习 C++,我刚刚进入虚函数。
From what I've read (in the book and online), virtual functions are functions in the base class that you can override in derived classes.
从我读到的(在书中和在线)中,虚函数是基类中的函数,您可以在派生类中覆盖它们。
But earlier in the book, when learning about basic inheritance, I was able to override base functions in derived classes without using virtual
.
但在本书前面,在学习基本继承时,我能够在不使用virtual
.
So what am I missing here? I know there is more to virtual functions, and it seems to be important so I want to be clear on what it is exactly. I just can't find a straight answer online.
那么我在这里错过了什么?我知道虚函数还有更多,而且它似乎很重要,所以我想弄清楚它到底是什么。我只是在网上找不到直接的答案。
回答by M Perry
Here is how I understood not just what virtual
functions are, but why they're required:
这就是我不仅理解什么是virtual
函数,而且理解为什么需要它们的方式:
Let's say you have these two classes:
假设你有这两个类:
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."; }
};
In your main function:
在您的主要功能中:
Animal *animal = new Animal;
Cat *cat = new Cat;
animal->eat(); // Outputs: "I'm eating generic food."
cat->eat(); // Outputs: "I'm eating a rat."
So far so good, right? Animals eat generic food, cats eat rats, all without virtual
.
到目前为止一切顺利,对吧?动物吃普通食物,猫吃老鼠,都没有virtual
。
Let's change it a little now so that eat()
is called via an intermediate function (a trivial function just for this example):
现在让我们稍微更改一下,以便eat()
通过中间函数调用它(仅用于此示例的普通函数):
// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }
Now our main function is:
现在我们的主要功能是:
Animal *animal = new Animal;
Cat *cat = new Cat;
func(animal); // Outputs: "I'm eating generic food."
func(cat); // Outputs: "I'm eating generic food."
Uh oh... we passed a Cat into func()
, but it won't eat rats. Should you overload func()
so it takes a Cat*
? If you have to derive more animals from Animal they would all need their own func()
.
哦哦...我们把一只猫传给了func()
,但它不会吃老鼠。你应该重载func()
所以它需要一个Cat*
?如果您必须从 Animal 派生更多动物,那么它们都需要自己的func()
。
The solution is to make eat()
from the Animal
class a virtual function:
解决方案是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."; }
};
Main:
主要的:
func(animal); // Outputs: "I'm eating generic food."
func(cat); // Outputs: "I'm eating a rat."
Done.
完毕。
回答by Steve314
Without "virtual" you get "early binding". Which implementation of the method is used gets decided at compile time based on the type of the pointer that you call through.
没有“虚拟”,您将获得“早期绑定”。使用该方法的哪个实现在编译时根据您调用的指针类型决定。
With "virtual" you get "late binding". Which implementation of the method is used gets decided at run time based on the type of the pointed-to object - what it was originally constructed as. This is not necessarily what you'd think based on the type of the pointer that points to that object.
使用“虚拟”,您将获得“后期绑定”。使用该方法的哪个实现是在运行时根据指向对象的类型决定的——它最初被构造为什么。根据指向该对象的指针的类型,这不一定是您所想的。
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"
EDIT- see this question.
编辑- 看到这个问题。
Also - this tutorialcovers early and late binding in C++.
此外 -本教程涵盖了 C++ 中的早期和后期绑定。
回答by Henk Holterman
You need at least 1 level of inheritance and a downcast to demonstrate it. Here is a very simple example:
您至少需要 1 级继承和向下转换来演示它。这是一个非常简单的例子:
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
}
回答by Cheers and hth. - Alf
You need virtual methods for safe downcasting, simplicityand conciseness.
您需要安全向下转换、简单和简洁的虚拟方法。
That’s what virtual methods do: they downcast safely, with apparently simple and concise code, avoiding the unsafe manual casts in the more complex and verbose code that you otherwise would have.
这就是虚拟方法所做的:它们安全地向下转换,使用明显简单和简洁的代码,避免在更复杂和冗长的代码中进行不安全的手动转换,否则您将拥有。
Non-virtual method ⇒ static binding
非虚方法⇒静态绑定
The following code is intentionally “incorrect”. It doesn’t declare the value
method as virtual
, and therefore produces an unintended “wrong” result, namely 0:
以下代码故意“不正确”。它没有将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;
}
In the line commented as “bad” the Expression::value
method is called, because the statically known type(the type known at compile time) is Expression
, and the value
method is not virtual.
在注释为“bad”的行Expression::value
中调用该方法,因为静态已知类型(编译时已知的类型)是Expression
,并且该value
方法不是虚拟的。
Virtual method ⇒ dynamic binding.
虚拟方法⇒动态绑定。
Declaring value
as virtual
in the statically known type Expression
ensures that the each call will check what actual type of object this is, and call the relevant implementation of value
for that dynamic type:
在静态已知类型中声明value
as确保每次调用都将检查这是什么实际类型的对象,并为该动态类型调用相关的实现: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;
}
Here the output is 6.86
as it should be, since the virtual method is called virtually. This is also called dynamic bindingof the calls. A little check is performed, finding the actual dynamic type of object, and the relevant method implementation for that dynamic type, is called.
这里的输出是6.86
它应该的样子,因为虚拟方法是虚拟调用的。这也称为调用的动态绑定。执行一些检查,找到对象的实际动态类型,并调用该动态类型的相关方法实现。
The relevant implementation is the one in the most specific (most derived) class.
相关的实现是最具体(派生最多)的类中的一个。
Note that method implementations in derived classes here are not marked virtual
, but are instead marked override
. They could be marked virtual
but they’re automatically virtual. The override
keyword ensures that if there is notsuch a virtual method in some base class, then you’ll get an error (which is desirable).
请注意,此处派生类中的方法实现没有被标记virtual
,而是被标记override
。它们可以被标记,virtual
但它们是自动虚拟的。该override
关键字确保如果有没有在一些基础类这样的虚拟方法,那么你会得到一个错误(这是可取的)。
The ugliness of doing this without virtual methods
在没有虚拟方法的情况下这样做的丑陋
Without virtual
one would have to implement some Do It Yourselfversion of the dynamic binding. It’s this that generally involves unsafe manual downcasting, complexity and verbosity.
如果没有virtual
,则必须实现一些Do It Yourself版本的动态绑定。这通常涉及不安全的手动向下转换、复杂性和冗长。
For the case of a single function, as here, it suffices to store a function pointer in the object and call via that function pointer, but even so it involves some unsafe downcasts, complexity and verbosity, to wit:
对于单个函数的情况,就像这里一样,在对象中存储一个函数指针并通过该函数指针调用就足够了,但即便如此,它仍然涉及一些不安全的向下转换、复杂性和冗长,即:
#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;
}
One positive way of looking at this is, if you encounter unsafe downcasting, complexity and verbosity as above, then often a virtual method or methods can really help.
看待这一点的一种积极方式是,如果您遇到上述不安全的向下转换、复杂性和冗长性,那么通常一个或多个虚拟方法确实可以提供帮助。
回答by Cheers and hth. - Alf
Virtual Functions are used to support Runtime Polymorphism.
虚拟函数用于支持运行时多态性。
That is, virtualkeyword tells the compiler not to make the decision (of function binding) at compile time, rather postpone it for runtime".
也就是说,virtual关键字告诉编译器不要在编译时做出(函数绑定的)决定,而是将其推迟到运行时”。
You can make a function virtual by preceding the keyword
virtual
in its base class declaration. For example,class Base { virtual void func(); }
When a Base Classhas a virtual member function, any class that inherits from the Base Class can redefinethe function with exactly the same prototypei.e. only functionality can be redefined, not the interface of the function.
class Derive : public Base { void func(); }
A Base class pointer can be used to point to Base class object as well as a Derived class object.
- When the virtual function is called by using a Base class pointer, the compiler decides at run-time which version of the function - i.e. the Base class version or the overridden Derived class version - is to be called. This is called Runtime Polymorphism.
您可以通过
virtual
在其基类声明中的关键字前面来使函数成为虚拟函数。例如,class Base { virtual void func(); }
当基类具有虚成员函数时,任何从基类继承的类都可以使用完全相同的原型重新定义函数,即只能重新定义功能,而不是函数的接口。
class Derive : public Base { void func(); }
基类指针可用于指向基类对象和派生类对象。
- 当使用基类指针调用虚函数时,编译器在运行时决定调用函数的哪个版本——即基类版本或重写的派生类版本。这称为运行时多态性。
回答by Alex Martelli
If the base class is Base
, and a derived class is Der
, you can have a Base *p
pointer which actually points to an instance of Der
. When you call p->foo();
, if foo
is notvirtual, then Base
's version of it executes, ignoring the fact that p
actually points to a Der
. If foo isvirtual, p->foo()
executes the "leafmost" override of foo
, fully taking into account the actual class of the pointed-to item. So the difference between virtual and non-virtual is actually pretty crucial: the former allows runtime polymorphism, the core concept of OO programming, while the latter does not.
如果基类是Base
,而派生类是Der
,则您可以拥有一个Base *p
实际指向 的实例的指针Der
。当你打电话p->foo();
,如果foo
是不虚,那么Base
的的版本,它执行,无视事实,p
实际上指向Der
。如果 foo是虚拟的,则p->foo()
执行 的“最叶”覆盖foo
,完全考虑到指向项的实际类。所以虚拟和非虚拟之间的区别实际上非常关键:前者允许运行时多态性,这是面向对象编程的核心概念,而后者则不允许。
回答by Ajay GU
Need for Virtual Function explained [Easy to understand]
虚函数的需要讲解【通俗易懂】
#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();
}
Output will be:
输出将是:
Hello from Class A.
But with virtual function:
但是使用虚函数:
#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();
}
Output will be:
输出将是:
Hello from Class B.
Hence with virtual function you can achieve runtime polymorphism.
因此,使用虚函数,您可以实现运行时多态性。
回答by Aryaman Gupta
I would like to add another use of Virtual function though it uses the same concept as above stated answers but I guess its worth mentioning.
我想添加 Virtual function 的另一种用法,尽管它使用与上述答案相同的概念,但我想它值得一提。
VIRTUAL DESTRUCTOR
虚拟破坏者
Consider this program below, without declaring Base class destructor as virtual; memory for Cat may not be cleaned up.
考虑下面的这个程序,没有将基类析构函数声明为虚拟;Cat 的内存可能无法清理。
class Animal {
public:
~Animal() {
cout << "Deleting an Animal" << endl;
}
};
class Cat:public Animal {
public:
~Cat() {
cout << "Deleting an Animal name Cat" << endl;
}
};
int main() {
Animal *a = new Cat();
delete a;
return 0;
}
Output:
输出:
Deleting an Animal
Deleting an Animal
class Animal {
public:
virtual ~Animal() {
cout << "Deleting an Animal" << endl;
}
};
class Cat:public Animal {
public:
~Cat(){
cout << "Deleting an Animal name Cat" << endl;
}
};
int main() {
Animal *a = new Cat();
delete a;
return 0;
}
Output:
输出:
Deleting an Animal name Cat Deleting an Animal
Deleting an Animal name Cat Deleting an Animal
回答by h0b0
You have to distinguish between overriding and overloading. Without the virtual
keyword you only overload a method of a base class. This means nothing but hiding.
Let's say you have a base class Base
and a derived class Specialized
which both implement void foo()
. Now you have a pointer to Base
pointing to an instance of Specialized
. When you call foo()
on it you can observe the difference that virtual
makes: If the method is virtual, the implementation of Specialized
will be used, if it is missing, the version from Base
will be chosen.
It is best practice to never overload methods from a base class. Making a method non-virtual is the way of its author to tell you that its extension in subclasses is not intended.
您必须区分覆盖和重载。如果没有virtual
关键字,您只会重载基类的方法。这意味着除了隐藏。假设您有一个基类Base
和一个派生类Specialized
,它们都实现了void foo()
. 现在你有一个指针Base
指向的一个实例Specialized
。当您调用foo()
它时,您可以观察到其中的区别virtual
:如果方法是虚拟的,Specialized
则将使用的实现,如果缺少,Base
则将选择版本 from 。最佳实践是永远不要从基类重载方法。使方法成为非虚拟方法是其作者告诉您它不打算在子类中进行扩展的方式。
回答by Ziezi
Why do we need Virtual Methods in C++?
为什么我们需要 C++ 中的虚拟方法?
Quick Answer:
快速回答:
- It provides us with one of the needed "ingredients"1for object oriented programming.
- 它为我们提供了面向对象编程所需的“成分” 1之一。
In Bjarne Stroustrup C++ Programming: Principles and Practice, (14.3):
在 Bjarne Stroustrup C++ 编程:原则与实践中,(14.3):
The virtual function provides the ability to define a function in a base class and have a function of the same name and type in a derived class called when a user calls the base class function. That is often called run-time polymorphism, dynamic dispatch, or run-time dispatchbecause the function called is determined at run time based on the type of the object used.
虚函数提供在基类中定义函数的能力,并在用户调用基类函数时调用派生类中具有相同名称和类型的函数。这通常称为运行时多态性、动态分派或运行时分派,因为调用的函数是在运行时根据所用对象的类型确定的。
- It is the fastest more efficient implementation if you need a virtual function call2.
- 如果您需要虚函数调用2 ,它是最快、更有效的实现。
To handle a virtual call, one needs one or more pieces of data related to the derived object3. The way that is usually done is to add the address of table of functions. This table is usually referred to as virtual tableor virtual function tableand its address is often called the virtual pointer. Each virtual function gets a slot in the virtual table. Depending of the caller's object (derived) type, the virtual function, in its turn, invokes the respective override.
为了处理虚拟调用,需要一个或多个与派生对象3相关的数据。通常的做法是添加函数表的地址。该表通常称为虚拟表或虚拟函数表,其地址通常称为虚拟指针。每个虚函数在虚表中都有一个槽位。根据调用者的对象(派生)类型,虚函数依次调用相应的覆盖。
1.The use of inheritance, run-time polymorphism, and encapsulation is the most common definition of object-oriented programming.
1.继承、运行时多态和封装的使用是面向对象编程最常见的定义。
2. You can't code functionality to be any faster or to use less memory using other language features to select among alternatives at run time. Bjarne Stroustrup C++ Programming: Principles and Practice.(14.3.1).
2. 您无法使用其他语言功能在运行时在备选方案中进行选择,从而将功能编码得更快或使用更少的内存。Bjarne Stroustrup C++ 编程:原理与实践。(14.3.1)。
3. Something to tell which function is really invoked when we call the base class containing the virtual function.
3. 当我们调用包含虚函数的基类时,可以判断哪个函数被真正调用。