关于模板的"实例化"的含义

Meaning of 'instantiation' with respect to templates

本文关键字:实例化 于模板      更新时间:2023-10-16

请注意,代码仅针对被调用的成员函数进行实例化。对于类模板,成员函数只有在使用时才会实例化。

以上引用自Addison Wesley的《C++模板》一书。

我想理解术语">代码被实例化"的含义。这是否意味着只保留一个特定的内存,或者只编译该代码或其他什么?

这是一个非常有趣的问题,应该在编译器如何处理模板的更广泛背景下进行。基本上,模板是编译器生成函数类的代码模式。模板可以用于生成从根本没有代码(如果从未使用过(到无限数量的实例化的任何内容。

模板不会直接编译成任何短实体(在C#泛型中是这样,但在C++中不是这样(,而是由编译器解析代码并保存在内存中,以防以后在处理当前翻译单元时使用。也就是说,编译器处理模板(斜体的template用于创建事物的模式的英文形式,而不是精确的C++含义(,并在需要时根据该模板创建代码(类或函数(实例化是编译器确定特定模板与特定参数集一起使用并对模板执行参数替换以生成要编译的类或函数并最终编译为模板的二进制代码的过程。

有两种类型的模板实例化,隐式显式含模板实例化的。我将从显式模板实例化开始,因为它更简单。当显式实例化模板(语法请谷歌(时,您会告诉编译器,您希望从该模板生成的代码应用于您提供的特定参数。在类模板的情况下,它强制实例化所有成员函数,这基本上意味着编译器将替换类型并将其结果编译为二进制对象。

另一方面,隐式实例化是按需执行的。当您使用类模板时,实际使用的位和块会从模板中生成,并编译到翻译单元中。如果创建变量定义std::vector<int> v;,编译器将类型int(和默认类型std::allocator<int>(应用于template std::vector并创建类型std::vector<int>,但在这样做的过程中,它不会编译所有成员函数,而是只编译那些需要的函数,在这种情况下,它将是默认构造函数std::vector<int>::vector()和析构函数std::vector<int>::~vector()。其余的方法不会被编译,二进制文件中也不会有它们的代码。

没有实例化所有成员函数有几个原因,原因包括复杂性,从简单到深入的语言细节。对于一些更简单的函数,您可以考虑编译的性能(不必仅仅因为使用了其中一个成员函数就生成/编译所有成员函数,这将大大缩短编译时间(。稍微复杂一点的是,模板中不同的成员函数可能会对实例化类型施加不同的要求。例如,map上的operator[]要求值的类型为默认可构造,因为如果映射中不存在新元素,则操作符将创建新元素。另一方面,如果始终使用findinsert,则可以将std::map与默认不可构造的类型一起使用。通过不强制编译所有成员函数,该语言允许您使用带有参数的模板,这些参数不能满足所有方法的所有要求,只要它确实满足实际使用的方法的要求即可。

我在上面的描述中使用了这个术语,但我还没有定义它。要想得到准确的定义,你必须参考标准,但是,如果一个成员函数是直接或间接从您的代码中调用的(即您调用它,或者您实例化的其他某个成员函数调用它(,或者您获得了该成员函数的地址,则可以认为该成员函数是使用的。

重要的一点是,类中的模板方法只有在某些代码调用时才会被编译。这意味着,如果没有人引用,则无法编译的模板方法不是问题。

就像一个将生成运行时错误的函数除非被调用,否则不会有问题一样,一个将在实例化时生成编译器错误的模板函数除非有人对其进行实例化,否则也不会有问题。例如:

#include <stdio.h>
#include <string>
template<typename T>
struct Foo
{
    const T& x;
    Foo(const T& x) : x(x) {}
    template<typename S>
    operator S () const
    {
        S s;
        s = x + 1;
        return s;
    }
};
int main()
{
    std::string s = "bar";
    int i = 42;
    Foo<std::string> fs(s);
    Foo<int> fi(i);
    printf("Here we go... -> %fn", double(fi));
    // printf("This won't compile -> %fn", double(fs));
    return 0;
}

此代码可编译,即使在实例化Foo<std::string>时,实例化到double的隐式转换也是非法的(因为x+1std::string是非法的(。

如果删除注释,则这是必需的,并且程序将不会编译。

我认为它指的是编译器从未查看过未使用的模板代码。这样做的结果是,例如,类模板中可能存在语法错误,如果代码中从未使用过该类,则不会导致编译错误。

用具体类型替换模板参数的过程称为实例化。它将生成模板的实例。即编译器为请求的类型的模板函数/类生成代码。

只有在使用(调用(了成员函数或者编译器根本没有生成代码的情况下,编译器才会为模板类的成员函数生成代码。这是对C++的基本原则的遵守,你为你使用的东西付费

书中的陈述解释了这一点。

。"代码被实例化"意味着代码被放入内存(代码段(。只有当代码中引用了特定方法/变量时,才会发生这种情况。

(术语Referred并不意味着该函数在运行时被调用。它意味着该方法存在于代码中的某个位置。因此,如果该方法是Referred,则它将被编译并发出相应的代码。(

例如,

template<typename T>
T add(T a, T b) { return a + b; }

现在,如果您在代码中引用类型double作为

double d = add(2.3, 4.6);

则编译器将仅针对CCD_ 17发出代码。发射add<int>()add<A>()的代码没有意义。