C++中virtual怎么用?

C++中virtual怎么用?,第1张

C++中的虚函数和纯虚函数用法

1虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。

2虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)

只有声明而没有定义。

3虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。

4虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。

5虚函数的定义形式:virtual {method body} ;纯虚函数的定义形式:virtual { } = 0; 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。

6如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

根据Effective C++ (3rd Edition) ---- Item 9:

Never call virtual functions during construction or destruction

(不要在构造函数和析构函数中调用虚函数)

其中举例为:(英文)

class Transaction {

public:

Transaction();

virtual void LogTransaction() const = 0;

//

};

Transaction::Transaction()

{

//

LogTransaction();

}

class BuyTransaction : public Transaction {

public:

virtual void LogTransaction() const;

//

};

Consider what happen when this code is executed:

BuyTransaction b;

Clearly a BuyTransaction constructor will be called, but firt, a Transaction constructor must be called; base class parts of derived class objects are constructed before derived class parts are The last line of the Transaction constructor calls the virtual function longTransaction, but this is where the surprise comes in The version of logTransaction that't called is the one in Transaction, not the one in BuyTransaction -- even though the type of object being created is BuyTransaction During base class construction, virtual functions never go down into derived classes Instead, the object behaves as if it were of the base type Informally speaking, during base class construction, virtual functions aren't

There's a good reason for this seemingly counterintuitive behavior Because base class constructors execute before derived class constructors, derived class data members have not been initialized when base class constructors run If virtual functions called during base class construction went down to derived classes, the derived class functions would almost certainly refer to local data members, but those data members would not yet have been initialized That would be a non-stop ticket to undefined behavior and late-night debugging sessions Calling down to parts of an object that have not yet been initialized is inherently dangerous so C++ gives you no way to do it

根据上面的原文看:调用的结果是(可能发生未定义行为)

这是由于(上例中)在BuyTransaction构造函数执行时,先执行基类(Transaction)的构造函数,而在Transaction的构造函数中又调用了(纯)虚函数logTransaction(),由于现在是在派生类里边定义函数,所以调用的logTransaction是BuyTransaction()里边的函数。这样有违背了你的初衷(调用Transaction里边的logTransaction())

如果在虚函数里边有对派生类成员(不是从基类那里继承来的)的 *** 作,那么更遭,以为在调用基类构造函数调时(派生类的成员还没有被初始化),这样就会导致未定义行为。

你最好看看原文的解释(英文部分)!

总之,记住不要在构造函数(析构函数)中调用虚函数!

你是说如果把base::Out改成Out会出错是吧?

因为如果不指明调用基类的虚函数,那么编译器就必须要等派生类同名的重载函数产生后,调用派生名的函数。而要生成派生类的Out函数,必须先生成派生类的实例。而生成派生类的实例,又必须先调用基类的构造函数。基类的构造函数中又调用了Out,而这时候派生类的实例还没有生成,相应的Out函数也还没生成,所以直接调用Out会导致无法链接到有效的派生类的Out函数,所以出错。

虚函数的作用就是接口类能够提供协议(即这个接口的功能是什么,比如都是进行绘画,相对来说比较抽象),而各个子类可以给出具体不同的实现(比如长方形绘画时可以画出长方形,而三角形则画出来的就是三角形)。调用虚函数的客户只需要知道虚函数的提供的协议,而不需要关心这个实现这个虚函数的到底是个什么类(因为都是从一个基类继承的)。

当然你可能会想,为什么不能用非虚函数做上面的事情,我们可以实现时指定一个类型,比如有一个成员用来标识形状的类型,而在实现时用这个类型来判定如何进行绘画,简而言之就是if else或者switch语句,如同下面的代码:

class shape {

   int type;

public:

   void draw() {

      switch (type) {

      case rectangle:

          draw_rectangle();

          break;

      case circle:

          draw_circle();

          break;

      }

};

这个问题在于,当你有一个很多shape时,这个switch case将难以管理,因为有时候代码中有些地方就是需要根据类型来做判断,但是此时你会倾向于使用switch case来实现这些代码。久而久之,一旦加个新类型时,就会很难维护,因为要改动很多地方,就容易出错(当然如果改动地方不多,问题不会很大)。

但是如果使用虚函数实现,那么各个子类管理自己的实现,而有需要依据类型来判断的地方,可以通过虚函数来实现,因为每个类可以定制这种行为,对于draw而言,可以

class rectangle : public shape {

public:

   virtual void draw() {

       // draw rectangle;

   }

};

class circle : public shape {

public:

   virtual void draw() {

        // draw circle

   }

};

这样当我们需要加新shape的时候,只需要添加一个新的子类,实现draw(或者其他依赖于类型的函数时),就不用到处去修改那些swtich case语句了,也不容易出错了。

敏捷开发一书中,提到的OCP(开放封闭原则),就可以用虚函数来做到。

没有办法。

c++中类的初始化顺序是先基类后子类,基类在初始化时子类还未初始化,因此子类的相关数据如虚函数表等都还未初始化,无法调用虚函数

另外,类的析构顺序是先子类后基类,因此在基类的析构函数中也不要调用子类的虚函数

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/11669704.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-17
下一篇 2023-05-17

发表评论

登录后才能评论

评论列表(0条)

保存