以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/2HXYlggENXcSwTdydtof0g
首先,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() 也被称呼为 虚拷贝构造函数
。
全文未结束,更多精彩欢迎关注我!