c++继承、工厂,可见性修饰符
- 以下demo示例不是很典型,但是使用工厂的一个例子,其中有一个问题,就是对于处于继承体系上的基类和派生类(实现)而言,在工厂模式中,究竟是开放基类的析构函数还是开放派生类的析构函数?
/**
* @brief C++类内protected、private构造、析构的艺术
* @note 背景知识:
* 1. 如果不想让基类被实例化的方式有:①让基类带有纯虚成员函数(成为接口);②限定基类的构造函数为非public
* 不讨论接口情况,若基类构造函数被声明为protected,则只能通过继承,并实例化派生类获取基类的特性
* 若基类构造函数限定为private(尚未学习)
* 2. 不想让类的实例被显式析构的方法:限定类的析构函数为非public
* 那么有一个问题,究竟是使基类的析构函数public还是派生类的析构函数public???
* 若限定基类的析构函数为private(尚未学习)
* 3. 还可以通过设置类的构造函数非public,自定义创建类对象函数,实现类一定在堆上创建(这里没有示例代码)
*
* @note 使用场景:
* 1. 使用工厂控制基类的析构,工厂的构造和析构函数都是公有的,因为工厂是可以被用户使用的,工厂的生死可由用户自由决定
*
* @brief C++多重继承的艺术
* 1. dynamic_cast<destType>(srcObj) 要求参数srcObj必须具有多态属性(至少含有一个虚函数,虚析构函数就可以)
*
* @date 2021/01/12
* @author lee
*/
#include <iostream>
#include <string>
using namespace std;
/* Product类声明---基类-用于规定业务(产品接口) */
class IProduct
{
public:
virtual void process(string name) = 0;
protected:
virtual ~IProduct() {};
};
/* 派生类 */
class CustomTV : public IProduct
{
public:
CustomTV();
virtual void process(string name) override;
public:
virtual ~CustomTV();
};
CustomTV::CustomTV() {}
CustomTV::~CustomTV() {}
/**
* @brief 具体产品实现产品接口定义的业务功能
*/
void CustomTV::process(std::string name)
{
cout << name << endl;
}
/* 基类---工厂类,创建CustomTV */
class TVFactory
{
public:
TVFactory();
virtual ~TVFactory();
IProduct * createTV();
void destroy(IProduct *product);
};
TVFactory::TVFactory() {}
TVFactory::~TVFactory() {}
/**
* @brief 工厂是用来创建产品的,所以工厂一定会有一个返回具体产品指针的对外接口
*/
IProduct * TVFactory::createTV()
{
CustomTV *newTV = new CustomTV();
return newTV;
}
void TVFactory::destroy(IProduct *product)
{
delete (CustomTV *)product;
}
int main()
{
// 对于用户来说,只需要知道产品的接口和工厂实现即可,也就是包含IProduct声明的头文件和工厂头文件
// 1.使用时,先创建工厂
TVFactory *f1 = new TVFactory();
// 2.由工厂创建具体产品
IProduct *tv1 = f1->createTV();
// 3.使用产品内的功能
tv1->process("Hello, world!");
// 4.销毁产品
f1->destroy(tv1);
delete f1;
return 0;
}
- virtual关键字
- virtual关键字用于修饰成员函数(虚方法)
virtual关键字修饰的基类成员函数,当使用基类的指针或引用调用基类成员函数时,会根据基类的指针或引用指向的具体对象,调用相应的实现函数。多态(虚方法特性)
class BaseClass { public: BaseClass(); virtual ~BaseClass(); virtual void func1(); }; class DerivedClass : public BaseClass { public: DerivedClass(); virtual ~DerivedClass(); virtual void func1() override; }; // 例子1,基类指针/引用指向基类对象 BaseClass *ptr = new BaseClass(); ptr->func1(); // 调用的是基类的func1() // 例子2 基类指针/引用指向派生类对象 BaseClass *ptr2 = new DerivedClass(); ptr2->func1(); // 调用的是派生类的func1();
- virtual关键字用于修饰析构函数
基类声明一个需析构函数,用于确保释放派生类对象时,按正确的顺序调用析构函数。(正确的析构顺序:先析构派生类,再析构基类)
在C++ primer plus第500页的实例代码里,有一个delete 基类指针的例子
- 如果基类析构函数不是虚的,将只调用对应于指针/引用类型的析构函数(非多态)
class BaseClass { public: BaseClass(); ~BaseClass(); virtual void func1(); }; class DerivedClass : public BaseClass { public: DerivedClass(); ~DerivedClass(); virtual void func1() override; }; /* 基类析构函数不是虚的 * 例子1 * 如果(基类)析构函数不是虚的,将会调用指针类型相对应的类的析构函数 * 这里就是BaseClass的析构函数 */ BaseClass *ptr1 = new BaseClass(); delete ptr1; /* 基类析构函数不是虚的 * 例子2 * 这里还是调用BaseClass的析构函数 * 而如果DerivedClass中非基类部分数据将不会被释放,最终导致内存泄露 */ BaseClass *ptr1 = new DerivedClass(); delete ptr1;
- 如果基类析构函数是虚的,将调用相应对象类型的析构函数,也就是析构与具体对象类型有关,与指针类型无关(多态)其实这也属于虚方法特性,虚析构函数也是类的虚方法
class BaseClass { public: BaseClass(); virtual ~BaseClass(); virtual void func1(); }; class DerivedClass : public BaseClass { public: DerivedClass(); virtual ~DerivedClass(); virtual void func1() override; }; // 例子1 BaseClass *ptr1 = new BaseClass(); delete ptr1; // 调用的是BaseClass的析构函数 // 例子2 BaseClass *ptr1 = new DerivedClass(); delete ptr1; // 调用的是DerivedClass的析构函数
- 小结
virtual关键字修饰的类成员函数(包括普通成员函数和析构函数),都具有虚方法特性,当使用基类指针/引用指向具体对象时,调用的虚方法会根据具体对象类型自行选择。
注意概念:具体对象类型 和 指针类型
现在来回答上面的问题“工厂模式中,究竟应该开放基类的析构函数还是开放派生类的析构函数?”
答案肯定是开放派生类的析构函数,那为什么呢?
首先,明确析构顺序:派生类->基类
第二,明确一旦析构函数非public,继承体系外不能调用析构函数
我们的目的是释放具体对象(也就是派生类对象),也就是释放顺序是确定的(具体对象->具体对象内部的基类对象),如果派生类析构函数是非public的,将无法析构具体对象,达不到目的,所以开放的一定是派生类的析构函数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ITBL9WYp-1610450554403)(file://D:\git_repository\dailylog\learn_log\f9f7a4b6e0a00899b70ecdd3733c82c58cc5fb9b.PNG)]
那第二个问题:基类的析构函数开不开放?
其实基类析构函数可以开放,但是我的答案是在工厂模式下设置为保护,为什么呢?(请看下面分析)
/* Product类声明---基类-用于规定业务(产品接口) */ class IProduct { public: virtual void process(string name) = 0; public: virtual ~IProduct() {}; }; /* 派生类 */ class CustomTV : public IProduct { public: CustomTV(); virtual void process(string name) override; public: virtual ~CustomTV(); }; CustomTV::CustomTV() {} CustomTV::~CustomTV() {} /** * @brief 具体产品实现产品接口定义的业务功能 */ void CustomTV::process(std::string name) { cout << name << endl; } int main() { IProduct * tv1 = new CustomTV(); // 如果基类析构函数设置为公有,则可直接delete基类指针指向的对象 delete tv1; return 0; }
上面代码中可以看到,当基类的析构函数是公有时,可以直接通过delete IProduct *基类指针释放具体的对象CustomTV。
通常我们暴露给用户的,只是接口,也就是IProduct类所在的头文件,然后提供给用户工厂以创建接口的具体对象,我们不希望用户直接能使用delete来释放具体对象,而是希望具体对象的释放也由工厂控制,所以,如果使用工厂模式时,基类的析构函数是非公有的。
而将基类的析构函数可见性设为protected,使用工厂的代码如下:
#include <iostream> #include <string> using namespace std; /* Product类声明---基类-用于规定业务(产品接口) */ class IProduct { public: virtual void process(string name) = 0; protected: virtual ~IProduct() { cout << "delete IProduct" << endl; }; }; /* 派生类 */ class CustomTV : public IProduct { public: CustomTV(); virtual void process(string name) override; virtual ~CustomTV(); }; CustomTV::CustomTV() {} CustomTV::~CustomTV() { cout << "delete CustomTV" << endl; } /** * @brief 具体产品实现产品接口定义的业务功能 */ void CustomTV::process(std::string name) { cout << name << endl; } /* 基类---工厂类,创建CustomTV */ class TVFactory { public: TVFactory(); virtual ~TVFactory(); IProduct * createTV(); void destroy(IProduct *product); }; TVFactory::TVFactory() {} TVFactory::~TVFactory() { cout << "delete TVFactory" << endl; } /** * @brief 工厂是用来创建产品的,所以工厂一定会有一个返回具体产品指针的对外接口 */ IProduct * TVFactory::createTV() { CustomTV *newTV = new CustomTV(); return newTV; } void TVFactory::destroy(IProduct *product) { delete (CustomTV *)product; } int main() { // 对于用户来说,只需要知道产品的接口和工厂实现即可,也就是包含IProduct声明的头文件和工厂头文件 // 1.使用时,先创建工厂 TVFactory *f1 = new TVFactory(); // 2.由工厂创建具体产品 IProduct *tv1 = f1->createTV(); // 3.使用产品内的功能 tv1->process("Hello, world!"); // 4.销毁产品 f1->destroy(tv1); // delete tv1; // 显式delete 会报错 delete f1; return 0; }
工厂代码里有两个地方要注意的:
第一是destroy函数实现时,delete前要将基类指针强制转换为派生类指针。
第二是createTV函数实现时,返回值是接口的指针,而不是具体类的指针
第三是工厂的释放由用户决定
C++定义一个只能创建在堆上的类
出发的原因是,如何使用类的可见性限制关键字,来创建一个只能创建在堆上,而不能创建在栈上的对象。
#include <iostream>
using namespace std;
class BaseClass
{
protected:
BaseClass();
~BaseClass();
public:
static BaseClass * create();
void destroy();
void test();
};
BaseClass::BaseClass() {}
BaseClass::~BaseClass()
{
cout << "successfully delete" << endl;
}
BaseClass * BaseClass::create()
{
return new BaseClass();
}
void BaseClass::destroy()
{
delete this;
}
void BaseClass::test()
{
cout << "Hello,world!" << endl;
}
int main()
{
// 一个只能创建在堆上的对象
BaseClass *bc1 = BaseClass::create();
bc1->test();
bc1->destroy();
return 0;
}
以上代码要注意的就是,BaseClass类的create函数必须为static,因为如果不用static关键字修饰的话,在调用该函数时,具体的对象还没被创建出来,是无法调用的。
- 单例模式简单实现—这个不一定标准
C++ 将对象的构造和析构函数声明为Protected的作用_碧海凌云的博客-CSDN博客
- date 2021/01/12