模板是如何实例化的?

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&lt;char&gt; 被实例化,但没有任何成员被实例化。

(b) Stack&lt;double&gt; 被实例化,但没有任何成员被实例化。

(c) Stack&lt;int&gt; 及其默认构造函数被实例化。

(d) (e) 完全不知道...

(f) Stack&lt; string &gt; 被实例化,但没有任何成员被实例化。

我说的对吗?谁能告诉我这段代码是如何实例化的?

【问题讨论】:

如果有疑问,您可以添加相关的静态断言(或类似的东西来强制编译时错误),例如断言Tvoid。然后,成员函数中的失败断言会告诉您该成员函数已实例化。当你知道会发生什么时,解释它发生的原因就变得容易多了。成员函数外部的失败断言同样会告诉您该类本身已实例化。 @hvd 谢谢。因为我以前从未使用过断言。我试图理解这种方法。在这种情况下,由于所有的复制和控制成员都是合成版本,我应该像默认构造函数那样编写自定义版本并将断言放入其中吗?如果它没有编译,这意味着这个默认构造函数被实例化了。我说的对吗? 是的,我就是这个意思。您首先检查(使用 David Kernin 的回答)在哪些情况下类本身会被实例化。然后,您可以将类(从答案复制断言样式)更改为,例如,template &lt;typename T&gt; 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&lt;int&gt; 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 和显式实例化的模板一起使用

带有 lambda 作为每个实例化的唯一默认参数的模板

函数申明对函数模板实例化的屏蔽

带有 Clang 10 显式模板实例化的 ~queue 的未定义引用

Django:RelatedManager 对象是如何实例化的?