关于C++工厂模式下可见性修饰符的杂谈

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关键字
  1. 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();
  1. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值