点击
要解决问题:
在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++皮毛, 除了虚函数, 好像没见过其它实现手段.
类型一旦抹除, 将无法保留, 但可以通过模板函数或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++
灵活性确实是可以支持你为所欲为.
点击