以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」mp.weixin.qq.com/s/2HXYlggEN…

首先,C++ 结构函数可所以虚函数吗?

语法上来说,答案是不可的。类目标的创立依赖于类结构函数的执行,在结构函数执行之前虚函数指针还是空的,虚函数指针需要被初始化才干运用,所以结构函数不能为虚函数。笔者之前有篇文章对此也做过相关刨析,有爱好可重视我然后查找阅读《刨析一下C++结构析构函数能不能声明为虚函数的背面机理?》。

那么为什么要说 C++ 结构函数也可所以虚函数?

这要回到需求上来分析,假设正在设计一个画板,咱们能够在上面添加编辑各种图形,比方方框、三角形、圆形等。

编辑自然包括仿制粘贴等操作,仿制的时分面临的是目标,因为类的多态特性,目标或许基于派生类实例化,拜访是经过基类指针变量,所以纷歧定会知道当时目标的具体类型。那么怎么仿制目标?

常规仿制目标

假如咱们知道目标目标的类型,那么仿制目标的常规做法是直接调用类的仿制结构函数(copy contrutor),看比如

#include <iostream>
using namespace std;
class Implementation
{
public:
    Implementation()
    {
        cout << "default constructor" << endl;
    }
    Implementation(const Implementation &other)
    {
        cout << "copy constructor" << endl;
    }
    Implementation& operator= (const Implementation &other)
    {
        cout << "operator=" << endl;
        return *this;
    }
};
int main()
{
    Implementation x;
    Implementation y = x;
    return 0;
}

欸,不是要演示仿制结构函数的调用吗,为什么上面的 main 函数里用的是等号 = 表达式?

留意:创立并初始化目标时调用的等号 = 不会调用赋值操作符,尽管类中已完成了赋值操作符,由下面的输出结果来看,实际上是调用了仿制结构函数。怎么区分什么情况下等号 = 表达式才会调用赋值操作符的完成?请记住,仿制结构函数用于初始化未存在的目标,赋值操作符用于替换已存在的目标的状况,两者差异的关键因素是目标是否已存在。而上面的比如中,因为等号 = 表达式是初始化目标,所以该目标是未存在的,天经地义便是调用了仿制结构函数。

output:

default constructor
copy constructor

动态克隆

假如咱们面临目标时,正如最初的画板中,仿制一个已存在的具体图形,可是不能确认其具体类型,又应该怎么仿制这些目标呢?

下面创立一些图形,根底图形特征用基类 BaseShape 表明,各种具体图形用 BaseShape 的派生类表明:

class BaseShape
{
    // ...
};
class Square : public BaseShape
{
    // ...
};
class Rectangle : public BaseShape
{
    // ...
};
int main()
{
    BaseShape *s1 = new Square();
    BaseShape *s2 = new Rectangle();
    // ...
   return 0;
}

在上面这个比如中,创立具体的图形目标,指针别离存放在基类指针变量 s1 和 s2 中。

在后续的运用中,仅仅依托基类目标指针,而且不清楚目标的创立类型,所以无法直接运用创立类型对应的仿制结构函数仿制目标。

可是接口依然能被基类指针调用,是否能够经过目标能直接调用的接口,赋予接口必定的魔法,利用类的多态特性完成动态仿制?

下面给基类 BaseShape 添加个接口(没有函数体完成的纯虚函数),为了凸显接口的意图—仿制目标,特意命名为 Clone(),并在派生类中给出完成:

class BaseShape
{
public:
   // ...
   virtual BaseShape *Clone() = 0;
};
class Square : public BaseShape
{
public:
   // ...
   Square *Clone()
   {
      return new Square(*this);
   }
};
class Rectangle : public BaseShape
{
public:
   // ...
   Rectangle *Clone()
   {
      return new Rectangle(*this);
   }
};

在派生类中的接口被重写时,能够直接调用各自的仿制结构函数,使得仿制目标又变得如此简单了,避免了语法上的约束。

假如你细心的话,会发现派生类对接口 Clone() 重写后回来值的类型与基类的声明不同。基类中回来接口 Clone() 的回来值是指向基类目标的指针,而各个派生类中接口 Clone() 重写后回来值别离是指向派生类目标的指针。这在 c++ 代码中是合法的,被称号为协差(Covariance)。

所谓协差(Covariance),便是基类虚函数的回来值为指向目标的指针时,派生类重写该虚函数而且回来值相同为指向目标的指针,前后两个回来的指针指向的目标类型能够不同,可是要求后一个类型(派生类)指针可转化为前一个类型(基类)的指针,也便是向上转化(Upcasting)。

从上面的代码可见,接口 Clone() 做的事情和仿制结构函数一样,都是仿制目标,但接口 Clone() 归于虚函数,所以,这个接口 Clone() 也被称号为 虚仿制结构函数

举一反三:虚结构函数

已然 虚仿制结构函数 能完成,那么 虚结构函数 也应该能够完成。

虚函数里的 ,指的是动态调用。虚函数在各个派生类中被重写,编译器无法确认哪个被重写的版别会被何时何地调用,只有在运行时,根据目标内部的虚函数指针指向来调用对应的版别,动态的特性说的便是这么一回事。

那么能够基于输入参数选择性调用结构函数,不也是运行时才干做的确认吗?所以 虚结构函数 在目的意义上不要求有必要是虚函数。

依照代码常规,一般派生类的目标指针都用基类指针变量保存,所以,能够在基类中定义一个静态成员函数,输入参数决定结构哪个派生类的目标:

class BaseShape
{
public:
   // ...
   static BaseShape *Create(int id);
};
BaseShape *BaseShape::Create(int id)
{
   switch (id) {
   case 1:
      return new Square;
   case 2:
      return new Rectangle;
   default:
      std::cout << "unkown id";
      return nullptr;
   }
}

上面代码中的静态成员函数 Create() 便是咱们心心念的 虚结构函数 了,内部经过参数选择地调用操作符 new 实例化对应的派生类目标,然后回来目标指针,回来的值依然经过向上转化(Upcasting)为基类指针类型。

尽管 C++ 语法上不允许类仿制结构函数或许结构函数声明为虚函数,但这不妨碍咱们对目标寻求的办法变通。

上面仅仅是经典的解决方案,还有现代版 C++ 的做法。欲知后事怎么,不妨重视我!