如何延迟类模板中虚函数的实例化以避免调用删除函数错误
问题描述
给定以下代码:
template <typename T> int badToCall() = delete; struct IA { virtual ~IA() {} virtual int foo() const = 0; }; template <typename T> struct A : public IA { virtual int foo() const override final { return badToCall<T>(); } }; int main() { A<int> a; }
如何延迟A<int>::foo的求值/实例化,使得实例化A<int>时不会触发“调用已删除函数”的错误?
有观点认为实例化A时必须构建虚表,会触发A::foo的提前实例化,但实际上仅需要A::foo的声明而非定义,就像常规非模板虚函数那样,理论上可以延迟完整实例化。
补充说明:若不使用模板,可针对int类型手动实现如下:
AInt.h
struct AInt: public IA { virtual int foo() const override final; };
实现放在cpp文件中,同时提供模板特化:
AInt.cpp
template<> int badToCall<int>() { return 1; } int AInt::foo() const { return badToCall<int>(); }
此时仅包含AInt.h就能实例化该结构体:
#include "AInt.h" int main() { AInt a; }
希望通过模板实现相同效果,无需手动定义AInt这类结构体,即能直接编写:
int main() { A<int> a; }
目标是延迟A<X>::foo的定义,理想情况下无需显式列出实例化。要求不调用A<X>::foo的编译单元不生成其代码,调用它的编译单元生成代码并自动去重。
解决方案
要实现该需求,核心是将A<T>::foo的声明与定义分离,避免实例化A<T>时强制实例化foo的定义,具体步骤如下:
1. 调整模板类A的头文件,仅保留foo的声明
修改头文件中的模板类定义,将foo的实现移出类体,仅保留声明:
// A.h template <typename T> int badToCall() = delete; struct IA { virtual ~IA() {} virtual int foo() const = 0; }; template <typename T> struct A : public IA { virtual int foo() const override final; // 仅声明,不定义 };
2. 在单独cpp文件中实现A<T>::foo与badToCall特化
将foo的模板定义和badToCall的特化放在单独的cpp文件中,确保只有包含该实现的编译单元才会实例化相关代码:
// A.cpp #include "A.h" // 为需要支持的类型特化badToCall template<> int badToCall<int>() { return 1; } // 提供A<T>::foo的模板定义 template <typename T> int A<T>::foo() const { return badToCall<T>(); } // 可选:显式实例化需要支持的A<T>类型,提前生成代码避免编译单元重复实例化 template struct A<int>;
3. 使用示例
主文件只需包含头文件即可实例化A<int>,不会触发错误:
// main.cpp #include "A.h" int main() { A<int> a; // 正常编译,仅实例化A<int>的类结构,未实例化foo的定义 // a.foo(); // 若调用foo,需确保链接时包含A.cpp的目标文件 }
原理说明
- 当模板类的成员函数仅在类体内声明、外部定义时,C++标准规定:只有当该函数被调用(或被ODR使用)时,才会触发其定义的实例化。
- 对于虚函数,虚表仅需要函数的地址占位,编译器可在链接阶段填充实际地址,无需在实例化类时就实例化函数定义,这与非模板虚函数的行为完全一致,匹配需求。
- 若不添加显式实例化,当某个编译单元调用
A<int>::foo时,编译器会自动实例化其定义;链接器会自动处理多个编译单元中重复实例化的代码,实现自动去重。
内容的提问来源于stack exchange,提问作者Alexander Torstling
相关产品推荐
相关产品推荐

