C++ 在模板类实例化时析构始终为virtual,普通接口类中必须主动指定virtual关键字

发布于:2023-01-08 ⋅ 阅读:(428) ⋅ 点赞:(0)

编译器默认析构函数在模板实例和普通接口类存在差异。

背景

在写如下的代码时,误将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;
}

调试查看虚函数表

gdb虚函数表结果
msvc虚函数表结果

结论

  1. 模板类实例化时析构函数默认为虚函数,不论模板实例类是否作为基类;
  2. 普通类编译时如果作为基类,析构函数必需要主动声明为virtual,-W 编译选项开启会有警告;
  3. 只要类中存在虚函数,均可看到虚函数表中有virtual析构(g++ 实现为两个,msvc实现只有一个);
  4. 模板类实例化时,除析构之外不存在其他虚函数时,gdb和msvc调试查看虚函数表为空,实际上虚析能够正确表现出虚函数的行为;
  5. 模板类实例化时,析构始终表现为虚函数行为;但如果不添加virtual关键字, msvc调试虚函数表中看不见析构; g++编译的无论是否添加virtual关键字,看见与否跟是否有除析构外的其他虚函数有关;
  6. 如果本测试有给贵佬带来正确有用的信息,希望不吝点赞
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到