假设我有一个计数秒的类SecondTimer
并通知侦听器,假设侦听器有一个接受来自计时器的事件的特殊方法onTimer()
:
template <class TName>
class SecondTimer
{
public:
SecondTimer() : listener(nullptr), stime(0), time(1000), isStarted(false) {}
void init(TName * _listener, int _stime)
{
GASSERT(_listener && "The listener can not be NULL");
listener = _listener;
stime = _stime;
time = 1000;
isStarted = false;
listener->onTimer(stime);
}
inline void start() { isStarted = true; }
inline void stop() { isStarted = false; }
void Process(int dtime)
{
if (!isStarted)
return;
time -= dtime;
if (time <= 0)
{
int ds = 1 - time / 1000;
time = time % 1000 + 1000;
stime -= ds;
listener->onTimer(stime);
//
if (stime <= 0)
{
isStarted = false;
}
}
}
private:
TName * listener;
int stime, time;
bool isStarted;
};
我想将它用作实现onTimer(int)
成员函数的1000个不同类的模板。
构建程序时会发生什么?编译器是否会复制所有1000个不同类的Init()
和Process()
函数?
答案 0 :(得分:3)
是。对于您使用它的每个类,编译器将实例化整个类。
这有时被称为模板膨胀。有一些技术可以部分避免它,现代编译器有时会做一些聪明的技巧。但是在一天结束时会有很多重复的代码。
有一些编译器(我不记得哪些编译器,如果他们碰巧编译两个相同的函数,字节到字节,他们会将它们合并为只有一个。这在技术上是非法的C ++,因为指针这些函数在不应该的时候会比较相等。但是模板类中的很多函数都会被合并。不幸的是,在你的例子中只有构造函数start()
和stop()
符合条件,其他的使用模板类很可能会产生不同的代码。
对于这类问题,您必须考虑使用模板或使用虚函数(接口)编写基类是否值得。在运行时(较慢和较小)或在编译期间(更快和更大)解决概念性虚拟调用onTimer
之间存在权衡。
例如:
struct TName
{
virtual void onTimer() =0;
};
class SecondTimer //no template bloat!
{
/*...*/
};
PS:我打算在这里添加一些关于哪些编译器进行合并的信息,但后来我发现this answer可以很好地解释它。
答案 1 :(得分:1)
编译器是否会为所有1000复制Init()和Process()函数 不同的班级?
是。这正是将要发生的事情。编译器将为类实例化整个代码,就像您手动编写了所有这些代码一样。然而,一些优化是可能的。