以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「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++ 的做法。欲知后事怎么,不妨重视我!