模板是如何实例化的?
Posted
技术标签:
【中文标题】模板是如何实例化的?【英文标题】:How is a template instantiated? 【发布时间】:2014-02-06 09:24:52 【问题描述】:这是C++ Primer 5th Edition中的一个练习:
练习 16.27:对于每个带标签的陈述,解释什么,如果有的话, 实例化发生。如果模板被实例化,请解释原因;如果 不,解释为什么不。第677页
template <typename T> class Stack ;
void f1(Stack<char>); // (a)
class Exercise
Stack<double> &rsd; // (b)
Stack<int> si; // (c)
;
int main()
Stack<char> *sc; // (d)
f1(*sc); // (e)
int iObj = sizeof(Stack< string >); // (f)
以下是我尝试过的:
(a) Stack<char>
被实例化,但没有任何成员被实例化。
(b) Stack<double>
被实例化,但没有任何成员被实例化。
(c) Stack<int>
及其默认构造函数被实例化。
(d) (e) 完全不知道...
(f) Stack< string >
被实例化,但没有任何成员被实例化。
我说的对吗?谁能告诉我这段代码是如何实例化的?
【问题讨论】:
如果有疑问,您可以添加相关的静态断言(或类似的东西来强制编译时错误),例如断言T
是void
。然后,成员函数中的失败断言会告诉您该成员函数已实例化。当你知道会发生什么时,解释它发生的原因就变得容易多了。成员函数外部的失败断言同样会告诉您该类本身已实例化。
@hvd 谢谢。因为我以前从未使用过断言。我试图理解这种方法。在这种情况下,由于所有的复制和控制成员都是合成版本,我应该像默认构造函数那样编写自定义版本并将断言放入其中吗?如果它没有编译,这意味着这个默认构造函数被实例化了。我说的对吗?
是的,我就是这个意思。您首先检查(使用 David Kernin 的回答)在哪些情况下类本身会被实例化。然后,您可以将类(从答案复制断言样式)更改为,例如,template <typename T> class Stack public: Stack() typedef typename T::ThisDoesntExist StaticAssert; ;
以检测默认构造函数的使用位置。
【参考方案1】:
在您的具体情况下,声明并不意味着实例化
#include <iostream>
using namespace std;
template <typename T> class Stack
typedef typename T::ThisDoesntExist StaticAssert; // T::ThisDoesntExist doesn't exist at all!
;
void f1(Stack<char>); // No instantiation, compiles
class Exercise
Stack<double> &rsd; // No instantiation, compiles (references don't need instantiation, are similar to pointers in this)
Stack<int> si; // Instantiation! Doesn't compile!!
;
int main()
Stack<char> *sc; // No Instantiation, this compiles successfully since a pointer doesn't need instantiation
f1(*sc); // Instantiation of Stack<char>! Doesn't compile!!
int iObj = sizeof(Stack< std::string >); // Instantiation of Stack<std::string>, doesn't compile!!
注意指针/引用的东西:它们不需要实例化,因为实际上没有分配数据(指针只是包含地址的几个字节,不需要存储所有数据......看看pimpl idiom)。
只有在分配了东西时,模板才必须被完全解析(这发生在编译时,这就是为什么它们通常需要声明和定义..还没有链接阶段)
【讨论】:
我以前从未使用过断言。在尝试从您的代码中编译语句Stack<int> si;
时,有人抱怨'int' is not a class, struct, or union type
。如何理解它?
如果您删除所有“不编译”行并将其注释掉,它应该可以编译:ideone.com/Amopp5。
另外:该特定行需要知道练习对象的有多大。如果您不实例化 si 对象,您无法提前知道这一点,而对于指针,它并不重要(在 x64 系统上,指针是 8 字节宽)。
所以……无论指针指向什么对象,指针的大小都是固定的,总是8字节。编译器将始终使用 8 字节空间来存储指针,即使在实例化之前也是如此。但是对于一个对象,编译器在实例化之前不会知道它的大小。你是这个意思吗?
在 x64 系统上,8 个字节。在 32 位 x86 系统上 4 个字节,依此类推,具体取决于架构。指针的类型很重要,但不知道应该提前分配多少空间。【参考方案2】:
关于 e 和 d 我将引用标准 14.7.1
除非函数模板特化已明确 实例化或显式特化的函数模板 当专业化是隐式实例化时 在需要函数定义存在的上下文中引用。 除非调用是对函数模板显式特化或 显式特化类模板的成员函数,a 函数模板或成员函数的默认参数 调用函数时隐式实例化类模板 在需要默认参数值的上下文中。
也来自标准的示例
template<class T> struct Z
void f();
void g();
;
void h()
Z<int> a; // instantiation of class Z<int> required
Z<char>* p; // instantiation of class Z<char> not required
Z<double>* q; // instantiation of class Z<double> not required
a.f(); // instantiation of Z<int>::f() required
p->g(); // instantiation of class Z<char> required, and instantiation of Z<char>::g() required
这意味着在 d 中没有实例化发生。如果该函数实际上需要调用该类型的函数(可以是复制构造函数或函数内部调用的任何其他函数),它将在 e 中实例化。
【讨论】:
以上是关于模板是如何实例化的?的主要内容,如果未能解决你的问题,请参考以下文章
将 pimpl 与 Templated Class 和显式实例化的模板一起使用