2023-11-03 C++ 类型擦除与状态保留


点击 快速C语言入门


C++ 类型擦除与状态保留

  • 前言
  • 一、一个正常的继承和多态
  • 二、一个不太正常的继承和多态
  • 三、试图构建能类型抹除但保留状态的mySharedPtr类
  • 总结


前言

要解决问题:
在C++中, 类型决定着对象的数据存储和解释, 以及方法.

通过继承和虚函数, 可以实现多态.

本文写一条邪路, 试图不用virtual修饰析构函数, 但在delete基类指针时, 调用子类的析构函数.

想到的思路:
我能想到的唯一思路, 是通过模板实现泛型, 通过lambda或函数指针抹除类型信息, 保留类型状态.

其它的补充:
本文完全是邪路子, 不要学, 只是作为C++灵活性的某种探讨, 虚函数才是正路.


一、一个正常的继承和多态

最简化模型, 只有析构函数, virtual修饰, 正常情况直接delete可以调用正确的析构函数, 使用shared_ptr也正确:

#include 
#include 

using std::shared_ptr;

class Base
{
  public:
    virtual ~Base()
    {
        printf("~Base()\n");
    }
};

class Derive : public Base
{
  public:
    ~Derive() override
    {
        printf("~Derive()\n");
    }
};

auto main() -> int
{
    Base *ptr = new Derive;
    delete ptr;

    shared_ptr<Base> test(new Derive);

    return 0;
}

二、一个不太正常的继承和多态

我们人为的不给基类析构函数加virtual修饰, 看看结果:

#include 
#include 

using std::shared_ptr;

class Base
{
  public:
    ~Base()
    {
        printf("~Base()\n");
    }
};

class Derive : public Base
{
  public:
    ~Derive()
    {
        printf("~Derive()\n");
    }
};

auto main() -> int
{
    Base *ptr = new Derive;
    delete ptr;

    shared_ptr<Base> test(new Derive);

    return 0;
}

结果是调用子类析构错误, 如果含有资源, 那么就已经泄漏了.

~Base()
~Derive()
~Base()  

然而, 你没看错, 使用shared_ptr智能指针, 没有错误析构, 非常奇怪,

细究起来, 除非能够在存储时抹除类型(化为基类指针), 调用时使用正确类型(化为子类指针)才能达到这种效果, C++有这种方法么, 鄙人愚拙, 实在是只掌握C++皮毛, 除了虚函数, 好像没见过其它实现手段.

三、试图构建能类型抹除但保留状态的mySharedPtr类

类型一旦抹除, 将无法保留, 但可以通过模板函数或lambda, 保留状态.

下面的代码就是用模板函数保留类型的状态,

其实就是利用模板的性质, 在编译期间, 产生一个接收void*万能指针, 但在内部能处理特定类型指针的函数, 详见funcDelete.

#include 
#include 

using std::shared_ptr;

class Base
{
  public:
    ~Base()
    {
        printf("~Base()\n");
    }
};

class Derive : public Base
{
  public:
    ~Derive()
    {
        printf("~Derive()\n");
    }
};

// 形参抹除指针类型, 内部保留类型状态
template <typename Temp>
void funcDelete(void *data)
{
    // 由于data类型信息被抹除, 无法使用dynamic_cast
    delete static_cast<Temp *>(data);
}

// 简化funcDelete函数指针类型
using delType = void (*)(void *);

template <typename Temp>
struct mySharedPtr
{
    // 构造函数, 抹除data类型, 通过funcDelete<>函数指针保存类型状态
    template <typename TempDerive>
    explicit mySharedPtr(TempDerive *data_)
        : data(data_)
    {
        deleter = funcDelete<TempDerive>;
    }

    // 析构函数, 调用有原始指针类型的deleter函数指针
    ~mySharedPtr()
    {
        deleter(data);
    }

  private:
    Temp *data = nullptr;
    delType deleter = nullptr;
};

auto main() -> int
{
    Base *ptr = new Derive;
    delete ptr;

    shared_ptr<Base> test(new Derive);

    mySharedPtr<Base> testB(new Derive);

    return 0;
}

还有更为简洁的, 或说更为C++的方法, 通过lambda抹除类型, 保留状态:

#include 
#include 
#include 

using std::shared_ptr;

class Base
{
  public:
    ~Base()
    {
        printf("~Base()\n");
    }
};

class Derive : public Base
{
  public:
    ~Derive()
    {
        printf("~Derive()\n");
    }
};

template <typename Temp>
struct mySharedPtr
{
    // 构造函数, 抹除data类型, 通过lambda保存类型状态, 也就是原始指针
    template <typename TempDerive>
    explicit mySharedPtr(TempDerive *data_)
        : data(data_)
    {
        deleter = [data_]() { delete data_; };
    }

    // 析构函数, 调用有原始指针类型的deleter函数指针
    ~mySharedPtr()
    {
        deleter();
    }

  private:
    Temp *data = nullptr;
    std::function<void()> deleter = nullptr;
};

auto main() -> int
{
    Base *ptr = new Derive;
    delete ptr;

    shared_ptr<Base> test(new Derive);

    mySharedPtr<Base> testB(new Derive);

    return 0;
}

通过lambda自行推断捕捉的变量类型, 并保留在内部状态中,

此操作很变态, 极度简洁且高效, 但我个人觉得有个小问题,

就是每个lambda都是一个类型, 这个和函数指针有天壤之别, 所以运行效率不一定比原始的函数指针好.


总结

通过上面示例, 我们实现了不用虚函数达到基类指针调用子类析构的目的, 比虚函数的实现还是要复杂一些, 并且不是太推荐, 只是作为一种尝试, 毕竟C++灵活性确实是可以支持你为所欲为.


点击 快速C语言入门


你可能感兴趣的:(笔记,c++,开发语言,类型抹除,状态保留)