编译器默认析构函数在模板实例和普通接口类存在差异。
背景
在写如下的代码时,误将62行的protecte声明为protect时,编译发现报错的析构函数为虚函数,但一直记得非模板类的析构默认不是虚函数,怀疑编译器(g++10.2 --std=c++2a)是对普通类的默认析构检测到作为基类时自动编译为虚函数,于是测试源代码如下,可直接拷贝在这里在线编译运行
测试代码
// test_template_virtual_destructor.cc
#include <functional>
#include <map>
#include <memory>
#include <exception>
#ifdef _WIN32
#include <windows.h>
#include <dbghelp.h>
#pragma comment(lib, "Dbghelp.lib")
#else
#include <cxxabi.h>
#endif
#include <iostream>
class noncopyable
{
noncopyable(const noncopyable &) = delete;
noncopyable& operator=(const noncopyable &) = delete;
protected:
noncopyable() = default;
virtual ~noncopyable() = default;
};
inline std::string GetClearName(const char* name)
{
#ifdef _WIN32
char buff[512]{ 0 };
auto ret = UnDecorateSymbolName(name, buff, sizeof(buff), UNDNAME_COMPLETE);
if (ret == 0) {
std::cout << "LastError: " << GetLastError() << std::endl;
return std::string(name);
}
return std::string(buff);
#else
int status = -1;
char* clear_name = abi::__cxa_demangle(name, NULL, NULL, &status);
const char* demangle_name = (status == 0) ? clear_name : name;
std::string ret_val(demangle_name);
free(clear_name);
return ret_val;
#endif
}
template <typename T> class Singleton : noncopyable
{
// using Self_T = Singleton<T>;
using Self_T = T;
public:
static Self_T &Instance()
{
static Self_T instance;
return instance;
}
virtual int fun(){
return 2;
};
protected:
Singleton()
{
std::cout << GetClearName(typeid(Self_T).name()) << std::endl;
};
~Singleton()
{
std::cout << "~" << GetClearName(typeid(Self_T).name()) << std::endl;
}
};
class Test
{
public:
virtual int fun()
{
return 1;
};
Test()
{
std::cout << "Test" << std::endl;
}
~Test() // error.
// virtual ~Test()
{
std::cout << "~Test" << std::endl;
};
};
class TestSon : public Test, public Singleton<Test>
{
public:
int fun()
{
return 0;
};
TestSon()
{
std::cout << "TestSon" << std::endl;
}
~TestSon()
{
std::cout << "~TestSon" << std::endl;
};
};
int main()
{
TestSon t1;
TestSon *t2 = new TestSon;
// Singleton<Test> tt; // protected constructor.
Singleton<Test> *tt1 = &t1;
Test *tt2 = t2;
// Singleton<TestSon> *tt1 = new TestSon1;
// Test *tt2 = new TestSon1;
std::cout << tt1->fun() << std::endl;
std::cout << tt2->fun() << std::endl;
Test &tt3 = Singleton<Test>::Instance();
std::cout << tt3.fun() << std::endl;
// delete tt1; // protected destructor.
delete tt2;
return 0;
}
调试查看虚函数表
结论
- 模板类实例化时析构函数默认为虚函数,不论模板实例类是否作为基类;
- 普通类编译时如果作为基类,析构函数必需要主动声明为virtual,-W 编译选项开启会有警告;
- 只要类中存在虚函数,均可看到虚函数表中有virtual析构(g++ 实现为两个,msvc实现只有一个);
- 模板类实例化时,除析构之外不存在其他虚函数时,gdb和msvc调试查看虚函数表为空,实际上虚析能够正确表现出虚函数的行为;
- 模板类实例化时,析构始终表现为虚函数行为;但如果不添加virtual关键字, msvc调试虚函数表中看不见析构; g++编译的无论是否添加virtual关键字,看见与否跟是否有除析构外的其他虚函数有关;
- 如果本测试有给贵佬带来正确有用的信息,希望不吝点赞
本文含有隐藏内容,请 开通VIP 后查看