我正在学习C++,我刚刚开始学习虚拟函数。
从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。
但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。
那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。
我正在学习C++,我刚刚开始学习虚拟函数。
从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。
但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。
那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。
当前回答
您需要虚拟方法来实现安全的下变频、简单和简洁。
这就是虚拟方法所做的:它们安全地向下转换,使用明显简单而简洁的代码,避免了在更复杂和冗长的代码中进行不安全的手动转换。
以下代码故意“不正确”。它没有将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;
}
在注释为“坏”的行中,调用了Expression::value方法,因为静态已知类型(编译时已知的类型)是Expression,而value方法不是虚拟的。
在静态已知类型表达式中将值声明为virtual可确保每次调用都会检查这是什么实际类型的对象,并调用该动态类型的值的相关实现:
#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。它们可以被标记为虚拟,但它们是自动虚拟的。override关键字确保如果某个基类中没有这样的虚拟方法,那么您将得到一个错误(这是可取的)。
如果没有虚拟绑定,则必须实现一些自己动手版本的动态绑定。这通常涉及不安全的手动降级、复杂性和冗长。
对于单个函数的情况,如这里所示,将函数指针存储在对象中并通过该函数指针进行调用就足够了,但即使如此,它也会涉及一些不安全的下变频、复杂性和冗长性,即:
#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;
}
看待这一点的一种积极方式是,如果您遇到了如上所述的不安全的下变频、复杂性和冗长,那么通常一个或多个虚拟方法确实会有帮助。
其他回答
下面是前两个答案的C++代码的合并版本。
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
#ifdef VIRTUAL
virtual string says() { return "??"; }
#else
string says() { return "??"; }
#endif
};
class Dog: public Animal
{
public:
string says() { return "woof"; }
};
string func(Animal *a)
{
return a->says();
}
int main()
{
Animal *a = new Animal();
Dog *d = new Dog();
Animal *ad = d;
cout << "Animal a says\t\t" << a->says() << endl;
cout << "Dog d says\t\t" << d->says() << endl;
cout << "Animal dog ad says\t" << ad->says() << endl;
cout << "func(a) :\t\t" << func(a) << endl;
cout << "func(d) :\t\t" << func(d) << endl;
cout << "func(ad):\t\t" << func(ad)<< endl;
}
两种不同的结果是:
如果没有#define virtual,它将在编译时绑定。Animal*ad和func(Animal*)都指向Animal的says()方法。
$ g++ virtual.cpp -o virtual
$ ./virtual
Animal a says ??
Dog d says woof
Animal dog ad says ??
func(a) : ??
func(d) : ??
func(ad): ??
使用#define virtual,它在运行时绑定。Dog*d、Animal*ad和func(Animal*)指向/引用Dog的says()方法,因为Dog是它们的对象类型。除非未定义[Dog's says()“woof”]方法,否则它将是在类树中首先搜索的方法,即派生类可能会覆盖其基类的方法[Eanimal's says)]。
$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual
Animal a says ??
Dog d says woof
Animal dog ad says woof
func(a) : ??
func(d) : woof
func(ad): woof
有趣的是,Python中的所有类属性(数据和方法)都是虚拟的。由于所有对象都是在运行时动态创建的,因此不需要类型声明或关键字virtual。下面是Python的代码版本:
class Animal:
def says(self):
return "??"
class Dog(Animal):
def says(self):
return "woof"
def func(a):
return a.says()
if __name__ == "__main__":
a = Animal()
d = Dog()
ad = d # dynamic typing by assignment
print("Animal a says\t\t{}".format(a.says()))
print("Dog d says\t\t{}".format(d.says()))
print("Animal dog ad says\t{}".format(ad.says()))
print("func(a) :\t\t{}".format(func(a)))
print("func(d) :\t\t{}".format(func(d)))
print("func(ad):\t\t{}".format(func(ad)))
输出为:
Animal a says ??
Dog d says woof
Animal dog ad says woof
func(a) : ??
func(d) : woof
func(ad): woof
这与C++的虚拟定义相同。注意,d和ad是两个不同的指针变量,引用/指向同一个Dog实例。表达式(ad is d)返回True,其值与0xb79f72cc>处的<main.Dog对象相同。
关键字virtual告诉编译器它不应该执行早期绑定。相反,它应该自动安装执行后期绑定所需的所有机制。为了实现这一点,典型的编译器1为每个包含虚拟函数的类创建一个表(称为VTABLE)。编译器将该特定类的虚拟函数的地址放在VTABLE中。在每个具有虚拟函数的类中,它都会秘密地放置一个指针,称为vpointer(缩写为VPTR),该指针指向该对象的VTABLE。当您通过基类指针进行虚拟函数调用时,编译器会悄悄地插入代码以获取VPTR并在VTABLE中查找函数地址,从而调用正确的函数并导致延迟绑定。
此链接中的详细信息http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html
OOP答案:亚型多态性
在C++中,需要虚拟方法来实现多态性,如果应用维基百科中的定义,则更准确地说是子类型或子类型多态性。
维基百科,分类,2019-01-09:在编程语言理论中,子类型化(也称为子类型多态性或包含多态性)是类型多态性的一种形式,其中子类型是通过某种可替代性概念与另一个数据类型(父类型)相关的数据类型,这意味着程序元素(通常是子例程或函数),编写为对父类型的元素进行操作也可以对子类型的元素执行操作。
注意:子类型表示基类,子类型表示继承类。
关于亚型多态性的进一步阅读
https://en.wikipedia.org/wiki/Subtypinghttps://en.wikipedia.org/wiki/Polymorphism_(computer_science)#子类型
技术答案:动态调度
如果您有一个指向基类的指针,那么方法的调用(声明为虚拟)将被分派到所创建对象的实际类的方法。这就是亚型多态性是如何在C++中实现的。
进一步阅读C++中的多态性与动态调度
http://www.cplusplus.com/doc/tutorial/polymorphism/https://en.cppreference.com/w/cpp/language/virtual
实现答案:创建vtable条目
对于方法上的每个修饰符“virtual”,C++编译器通常会在声明方法的类的vtable中创建一个条目。这就是常见的C++编译器实现动态调度的方式。
进一步阅读vtables
https://en.wikipedia.org/wiki/Virtual_method_table
示例代码
#include <iostream>
using namespace std;
class Animal {
public:
virtual void MakeTypicalNoise() = 0; // no implementation needed, for abstract classes
virtual ~Animal(){};
};
class Cat : public Animal {
public:
virtual void MakeTypicalNoise()
{
cout << "Meow!" << endl;
}
};
class Dog : public Animal {
public:
virtual void MakeTypicalNoise() { // needs to be virtual, if subtype polymorphism is also needed for Dogs
cout << "Woof!" << endl;
}
};
class Doberman : public Dog {
public:
virtual void MakeTypicalNoise() {
cout << "Woo, woo, woow!";
cout << " ... ";
Dog::MakeTypicalNoise();
}
};
int main() {
Animal* apObject[] = { new Cat(), new Dog(), new Doberman() };
const int cnAnimals = sizeof(apObject)/sizeof(Animal*);
for ( int i = 0; i < cnAnimals; i++ ) {
apObject[i]->MakeTypicalNoise();
}
for ( int i = 0; i < cnAnimals; i++ ) {
delete apObject[i];
}
return 0;
}
示例代码输出
Meow!
Woof!
Woo, woo, woow! ... Woof!
代码示例的UML类图
下面是一个完整的示例,说明了为什么使用虚拟方法。
#include <iostream>
using namespace std;
class Basic
{
public:
virtual void Test1()
{
cout << "Test1 from Basic." << endl;
}
virtual ~Basic(){};
};
class VariantA : public Basic
{
public:
void Test1()
{
cout << "Test1 from VariantA." << endl;
}
};
class VariantB : public Basic
{
public:
void Test1()
{
cout << "Test1 from VariantB." << endl;
}
};
int main()
{
Basic *object;
VariantA *vobjectA = new VariantA();
VariantB *vobjectB = new VariantB();
object=(Basic *) vobjectA;
object->Test1();
object=(Basic *) vobjectB;
object->Test1();
delete vobjectA;
delete vobjectB;
return 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;
}
在注释为“坏”的行中,调用了Expression::value方法,因为静态已知类型(编译时已知的类型)是Expression,而value方法不是虚拟的。
在静态已知类型表达式中将值声明为virtual可确保每次调用都会检查这是什么实际类型的对象,并调用该动态类型的值的相关实现:
#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。它们可以被标记为虚拟,但它们是自动虚拟的。override关键字确保如果某个基类中没有这样的虚拟方法,那么您将得到一个错误(这是可取的)。
如果没有虚拟绑定,则必须实现一些自己动手版本的动态绑定。这通常涉及不安全的手动降级、复杂性和冗长。
对于单个函数的情况,如这里所示,将函数指针存储在对象中并通过该函数指针进行调用就足够了,但即使如此,它也会涉及一些不安全的下变频、复杂性和冗长性,即:
#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;
}
看待这一点的一种积极方式是,如果您遇到了如上所述的不安全的下变频、复杂性和冗长,那么通常一个或多个虚拟方法确实会有帮助。