C++模板编程(20)---C++实例化模型C++ Instantiation Model

所谓template实例化,是适当地替换template parameters,以便从template获得常规class或常规function的过程。听起来平淡无奇,但实际上这个过程涉及极多细节。

1. 两段式查询two-phase lookup

    当编译器对template进行parsing词法分析时,无法解析受控名称dependent names。这些受控名称在对具现点被再次查询lookup。然而非受控名称non-dependent names会较早被查询,如此一来当template首次被编译器看到时,就可以较多地诊断处错误。这就是两段式查询概念:第一阶段发生在template parsing词法解析时刻,第二阶段发生在实例化时刻。

    在第一阶段编译器会查询非受控名称non-dependent names,这个过程会用到常规查询规则ordinary lookup规则:如果情况适用也会动用ADL(argument-dependent lookup)规则。非受饰的受控名称(unqualified dependent names;由于它们在一个带有dependent arguments的函数调用中看起来像个函数名称,所以是受控的,dependent)也会以此方式被查询,然而其结果并不完备,因此会在template实例化时再次被查询。

      在第二阶段,也就是一个所谓具现点point of instantiation, POI处,编译器会查询受控受饰名称(dependent qualified names;将特定具现体的template parameter代换为template arguments),也会对非受饰受控名称(unqualified dependent names)额外加以ADL查询。

2. 具现点Point of Instantiation,POI

我们已经说过,template程序代码中有这样一些位置点:C++编译器在这些点上必须能够取得某个template物体的声明或定义。一旦程序代码需要用到template的特化体,而编译器需要见到该template的定义以创造出该特化体时,就会在这个具现点POI上进行具现过程。具现点POI就是替换之后之模板template可插入的源码位置点。例如:

class MyInt {

public:

        MyInt(int i);

};

MyInt operator - (MyInt const &);

bool operator > (MyInt const&, MyInt const&);

typedef MyInt Int;

template <typename T>

void f(T i)

{

        if(i > 0) {

                g(-i);

        }

}

//(1)

void g(Int)

{

        //(2)

        f<Int>(42); //调用点

        //(3)

}

// (4)

当编译器看到调用动作f<Int>(42),它必须将T替换为MyInt来实例化template f。这于是就产生了一个POI,具现点。(2)与(3)距离很近,但它们不能作为POI,因为C++语法不允许在这两处插入::f<Int><Int>的定义。(1)和(4)的本质差异在于函数g(Int)在4处可见,于是template dependent call g(-i) 可被解析出来。如果把(1)当作POI,g(-1)就无法成功解析出来。幸运的是,针对“a reference to a nonclass specialization”, C++设计的POI是在最内层之namespace 作用域的声明或定义式的紧临后方。本例中这个POI就是(4)。

大家可能奇怪为什么本例使用MyInt而不是使用int。答案是在POI处进行的第二阶段查询只动用ADL。由于int并无相应的namespace,不会发生POI查询,从而编译器无法找到函数g()。如果你把Int的 typedef换成:

typedef int Int;

上述例子就无法通过编译。

template <typename T>

class S

{

public:

        T m;

};

//(5)

unsigned long h()

{

        //(6)

        return (unsigned long) sizeof (S <int>);

        //(7)

}

//(8)

函数作用域内的(6)和(7)不能作为POI,因为class S<int> (它构成一个namespace作用域)的定义式不能出现在这两个地方(原因是template不能在函数作用域内出现)。如果按照nonclass的方式思考,POI将是(8),但这样就造成算式S<int>不合法,因为在(8)之前S<int>的大小无从得知。因此对于“a reference to a template-generated class instance”,它们的POI位置被定义为:“最内层之namespace作用域(内含该class instance)“的声明语句或定义式的紧邻前方。本例中这个点是(5)。

当模板被实际实例化,有可能引发其他实例化动作。考虑下面例子:

template <typename T>

class S {

public:

        typedef int I;

};

//(1)

template <typename T>

void f()

{

        S<char>::I var1 = 41;

        typename S<T>::I var2 =42;

}

int main()

{

        f<double> ();

}

// (2) : (2a), (2b)

根据先前讨论,f<double>的POI是(2)。function template f()也用到了s<char> 这个class特化体,其POI在(1),还用到了S<T>,但在(1)处S<T>仍然受控(dependent),因此在(1)还不能进行实例化。然而,如果我们在(2)处实例化f<double> 我们必须同时实例化S<double>。这种级次POI(second POI, 或称transitive POI)的定义稍有不同。针对nonclass对象,次级POI和主POI完全相同,但针对class对象,次级POI紧邻其主POI之前。

上个例子中f<double>的POI在2(b),其前先的2(a)是S<double>的二级POI。

    在一个编译单元中,同一个具现往往有多个POI。针对class template实体,编译器只保留头一个POI。忽略所有后续POI(编译器并不真正认为它们是POI)。针对nonclass实体。所有POI都被保留。无论哪种情况,ODR(单一定义原则)都要求:被编译器保留的所有POI彼此都必须等价,但编译器不需要检验是否有违例情况。编译器可以有一个nonclass POI进行真正的实例化动作,而不需要担心其他POI可能导致其他具现体。

    事实上,大多数编译器都会把nonline function templates的真正实例化过程推迟到编译单元尾端。这也就是把相应的template特化体的POI移到了编译单元尾端。C++编译器实现者认为,这是一个合法的实现技术,但C++ 标准对此并无明确态度。

3. 置入式inclusion和分离式Seperation模型

    无论何时遇到一个POI,编译器必须能够取得与之对应的template的定义。对于class 特化体而言,这意味着当前编译单元中,class template的定义式必须出现在POI之前。对nonclass POI的态度虽然也如此,但典型情况是nonclass template的定义式被放在表头文档中。由当前编译单元以#include将它包含进来。这种template定义式的源码组织方式被称为置入式模型inclusion model,也是大家比较喜欢采用的模型。

        针对非类POI,还存在另一种方式: nonclass template 可被声明为export,并定义于另一个编译单元。这种方式称为分离式模型(separation model)。下面例子展示这种情况,仍然用的是max():

//Unit 1

#include <iostream>

export template <typename T>

T const & max(T const&, T const&);

int main()

{

        std::cout << max(7, 42) << std::endl; //(1)

}

// unit2

export template <typename T>

T const& max(T const& a, T const& b)

{

        return a < b ? b:a;

}

当第一个文件被编译时,编译器认为(1)是POI,此时T被替换为int。编译器必须确保编译单元2中的max()定义式被实例化,从而满足POI的需求。

4. 跨越编译单元寻找POI

假设上述的编译单元1被重写为:

//unit 1

#include <iostream>

export template <typename T>

T const & max(T const&, T const &);

namespace N

{

        class I

        {

         public:

                I(int i): v(i) {}

                int v;

        };

        bool operator < (I const & a, I const & b) {

                return a.v < b.v;

        }

}

int  main()

{

        std::cout << max(N::I(7), N::I(42)).v << std::endl; //(3)

}

POI 诞生于(3)处,而且编译其需要编译单元2中的max()定义。然而max()使用重载的operator< ,而后者却是在编译单元1中被声明,不可见于编译单元2.为了正确处理这种情况,编译器必须在实例化过程中参考两个不同的声明上下文declaration context:一是template定义于何处;二是类型I声明于何处。为涉入这个两个上下文,编译器使用两段式查询来对付template中的名称。

    第一阶段发生在template被parsing词法分析时候,换句话说在C++编译器首次见到template定义式时候,在此阶段,编译器会使用ordinary lookup规则和ADL规则来查询非受控名称non-dependent names。另外,编译器还使用ordinary lookup规则来查询受控函数dependent function,因为其函数自变量受控)的非受饰名称,但它会记住查询结果,不企图进行重载解析。重载解析是在第二阶段后进行的。

    第二阶段发生在POI。在POI处,编译器会使用ordinary lookup 规则和ADL规则来查询受控受饰名称dependent qualified names。至于受控非受饰名称dependent unqualified names 已经在第一阶段以ordinary lookup 查询过,编译器只运用ADL规则去查询,再把查询结果结合第一阶段的结果。这两个结果构成的集合将被用来完成重载函数的解析过程overload function resolution。

5.举例

第一例子是置入式模型的inclusion model的简单情况:

template <typename T>

void f1(T x)\

{

        q1(x); //(1)
}

void g1(int)

{

}

 int main()

{

        f1(7); //Error, 找不到g1()

        //(2) f1<int>(int) 的POI

}

调用f1(7)会制造出一个POI,位于main()的紧邻外侧(2)处。这个实例化过程的关键在于函数g1()的查询。

    当 template f1的定义式首次出现,编译器会注意到非受饰名称g1是受控的dependent,因为它是带有受控自变量(dependent argument):自变量的x类型取决于template parameter T的函数调用实参的函数名称。因此在(1)处使用ordinary lookup查询规则找g1,此刻不可见。在(2)处,也就是POI,编译器再次于相应的namespace和classes中查询g1,但因g1()唯一的自变量是int类型,而int类型没有相应的namespace和classes,所以编译器最终没有找到g1的完整类型。

第二个例子:

//common.hpp

export template <typename T>

void f(T);

class A {

};

class B{

};

class X{

public:

        operator A() {return A();}

        operator B() {return B();}

};

//a.cpp

#include "common.hpp"

void g(A)

{

}

int main()

{

        f<X>(X());

}

//b.cpp

#include "common.hpp"

void g(B)

{

}

export template <typename T>

void f(T x)

{

        g(x);

}

在文件 a.cpp中,main() 调用 f<X>(X()).f是个exported template,定义于文件b.cpp,

其内的调用动作g(x)被自变量类型x实例化。g()被查询两次:第一次使用ordinary lookup在文件b.cpp中查询(模板解析时候),第二次使用ADL规则在文件a.cpp中查询(模版实例化动作所在)

第一次查询找到g(B);第二次查询找到g(A). 两次都是合理的,因此调用具有二义性,不合法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第0章 导读(译者的话) 第1章 关于对象(Object Lessons) 加上封装后的布局成本(Layout Costs for Adding Encapsulation) 1.1 C++模式模式(The C++ Object Model) 简单对象模型(A Simple Object Model) 表格驱动对象模型(A Table-driven Object Model) C++对象模型(Th e C++ Object Model) 对象模型如何影响程序(How the Object Model Effects Programs) 1.2 关键词所带来的差异(A Keyword Distinction) 关键词的困扰 策略性正确的struct(The Politically Correct Struct) 1.3 对象的差异(An Object Distinction) 指针的类型(The Type of a Pointer) 加上多态之后(Adding Polymorphism) 第2章 构造函数语意学(The Semantics of constructors) 2.1 Default Constructor的建构操作 “带有Default Constructor”的Member Class Object “带有Default Constructor”的Base Class “带有一个Virual Function”的Class “带有一个virual Base class”的Class 总结 2.2 Copy Constructor的建构操作 Default Memberwise Initialization Bitwise Copy Semantics(位逐次拷贝) 不要Bitwise Copy Semantics! 重新设定的指针Virtual Table 处理Virtual Base Class Subobject 2.3 程序转换语意学(Program Transformation Semantics) 明确的初始操作(Explicit Initialization) 参数的初始(Argument Initialization) 返回值的初始(Return Value Initialization) 在使用者层面做优(Optimization at the user Level) 在编译器层面做优(Optimization at the Compiler Level) Copy Constructor:要还是不要? 摘要 2.4 成员们的初始队伍(Member Initialization List) 第3章 Data语意学(The Semantics of Data) 3.1 Data Member的绑定(The Binding of a Data Member) 3.2 Data Member的布局(Data Member Layout) 3.3 Data Member的存取 Static Data Members Nonstatic Data Member 3.4 “继承”与Data Member 只要继承不要多态(Inheritance without Polymorphism) 加上多态(Adding Polymorphism) 多重继承(Multiple Inheritance) 虚拟继承(Virtual Inheritance) 3.5 对象成员的效率(Object Member Efficiency) 3.6 指向Data Members的指针(Pointer to Data Members) “指向Members的指针”的效率问题 第4章 Function语意学(The Semantics of Function) 4.1 Member的各种调用方式 Nonstatic Member Functions(非静态成员函数) Virtual Member Functions(虚拟成员函数) Static Member Functions(静态成员函数) 4.2 Virtual Member Functions(虚拟成员函数) 多重继承下的Virtual Functions 虚拟继承下的Virtual Functions 4.3 函数的效能 4.4 指向Member Functions的指针(Pointer-to-Member Functions) 支持“指向Virtual Member Functions”之指针 在多重继承之下,指向Member Functions的指针 “指向Member Functions之指针”的效率 4.5 Inline Functions 形式对数(Formal Arguments) 局部变量(Local Variables) 第5章 构造、解构、拷贝 语意学(Semantics of Construction,Destruction,and Copy) 纯虚拟函数的存在(Presence of a Pure Virtual Function) 虚拟规格的存在(Presence of a Virtual Specification) 虚拟规格中const的存在 重新考虑class的声明 5.1 无继承情况下的对象构造 抽象数据类型(Abstract Data Type) 为继承做准备 5.2 继承体系下的对象构造 虚拟继承(Virtual Inheritance) 初始语意学(The Semantics of the vptr Initialization) 5.3 对象复制语意学(Object Copy Semantics) 5.4 对象的功能(Object Efficiency) 5.5 解构语意学(Semantics of Destruction) 第6章 执行期语意学(Runting Semantics) 6.1 对象的构造和解构(Object Construction and Destruction) 全局对象(Global Objects) 局部静态对象(Local Static Objects) 对象数组(Array of Objects) Default Constructors和数组 6.2 new和delete运算符 针对数组的new语意 Placement Operator new的语意 6.3 临时性对象(Temporary Objects) 临时性对象的迷思(神话、传说) 第7章 站在对象模型的类端(On the Cusp of the Object Model) 7.1 Template Template的“具现”行为(Template Instantiation) Template的错误报告(Error Reporting within a Template) Template中的名称决议方式(Name Resolution within a Template) Member Function的具现行为(Member Function Instantiation) 7.2 异常处理(Exception Handling) Exception Handling快速检阅 对Exception Handling的支持 7.3 执行期类型识别(Runtime Type Identification,RTTI) Type-Safe Downcast(保证安全的向下转型操作) Type-Safe Dynamic Cast(保证安全的动态转型) References并不是Pointers Typeid运算符 7.4 效率有了,弹性呢? 动态共享函数库(Dynamic Shared Libraries) 共享内存(Shared Memory)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值