模版(template)设计的初衷,是设计一种自动实例化机制,不需要使用者参与,编译器可根据使用者提供的模版参数再套用类的定义来实例化。所谓实例化,除了包含对于程序变量的实例化,即开辟空间并设置某些变量的初值(构造函数)以及指针(如vptr)以及其他支持(virtual base class offset),还有对于函数的实例化,即根据函数的定义生成机器指令,并在函数调用处提供函数的入口地址。简单来看,普通类和模版类的区别,在于普通类实例化时有较为固定的空间开销(除非类似new string(n)这样的实例化)。

为了实现这样的“自动实例化”,就需要许多操作都被推迟到模板类及其成员函数真正被使用的时候。然而就是这个要求,带来了许多问题:

1  编译器如何找到类那些成员函数?

这一点和普通类一样啦,如果函数定义在类内,就容易了;如果在同文件里的类外,根据::符号也能轻易发现;如果在其他文件,则需要借助文件的命名了,比如vector.h里声明的函数,必须在vector.cpp里定义;

在编译的过程中,一旦类成员函数被发现,则会作如下修改:在参数列表的头部加上为this指针准备的指针域,并将函数按照一定规则重命名以便能识别函数的所有者、是否是static、是否是const等信息。

一个成员函数(包括重载运算符),其原型最终一定是这个样子:

return_type function_name(class_name * ptr, arg_list)

或者,如果打开NRV优化:

 void function_name(class_name * ptr, return_type & __reult, arg_list)

注意,虽然NRV要把__result加到函数的形参列表里面,但this依然是在列表中的第一个位置,这样对于所有的成员函数,不管有没有做NRV,把第一个参数取出来总是能找到调用该函数的对象。

2  如何只实例化出被调用的成员函数?

有些编译器的办法是对所有的函数,深度地检查语法语义并最终实例化出来,然后借助链接器把实际使用的函数链接进去,其他函数就这样被无情抛弃,然而G++似乎不是这样做的:G++检查实际用了哪些函数,然后针对实际使用的函数进行深度的语法语义的检查和实例化。

是的,G++对于没使用的函数几乎一概不管,即使其中有明显的错误,有例为证:

 template <class T>
class C
{
public:
int cc;
void f_has_error();
void f_has()
{
}
}; template <class T>
void C<T>::f_has_error()
{
this->jgfhvgkfhgfkgjhgjhjghjfhghjghjgjkgkghjhj;
}

在这个例子中,模版类有两个函数,其中一个是正常的,另一个则使用了一个未定义的变量。如果C是普通类,那么一定会报错的,但是C现在是模版类,C的数据成员和函数成员的实例化都被推迟到了被使用的时候。所以,当main函数这么写:

main()
{
C<int> c;
c.f_has();
}

的时候,编译器毫无怨言。这是个不好的事情,毕竟编译器对于有错的代码没有给出任何提示。不过,编译器不是什么都不检查,例如如果这么写:

template <class T>
void C<T>::f_has_error()
{
sjdhakhflkashnfsdhaghs;
this->jgfhvgkfhgfkgjhgjhjghjfhghjghjgjkgkghjhj;
}

在这个例子中f_has_error直接用了个没声明的变量,“哦?这里捡到一只变量叫做sjdhakhflkashnfsdhaghs,这是个什么(翻一翻符号表)?不知道,报错!”,于是编译器还是发现了这个错误。而在之前的例子中,编译器看到了this指针。“哦?这是个指针,使用者访问了this指向的实例的某个内部成员,OK pass。” 至于那个成员叫做什么在哪里放着?一概不管。

3  编译器如何避免对于一个数据成员或成员函数的定义,在多个.o文件中被实例化

和1类似,或者通通实例化(实例化意味着将对变量和函数做深度的检查),然后借助链接阶段来取舍;或者通过模拟链接操作,找出实际上需要实例化的是哪些成员。

4  模版声明区(scope of template declaration)和模版实例化区(scope of template instantiation)中的同名函数的抉择

类都有声明的代码以及使用它的代码,因此上面这两个概念很容易理解。当有同名的函数出现在这两个区里面的时候,编译器如何决定用哪一个函数呢?

沿用《Inside the C++ object》书中的代码,现在假设类的声明区有如下代码:

extern double foo(double v)

template<class T>
class ScopeRules
{
int _val;
T _member;
public:
void invariant()
{
_member = foo( _val );
}
T type_independent()
{
return foo(_member);
}
};

而在实例化区有如下代码

extern int foo(int);

ScopeRules<int> sr0;

....

sr0.invariant();
sr0.type_independent();

在实例化区中的两个函数调用,分别调用了哪个foo呢?编译器解析时,将按照函数的参数是否和class T有关来决策:如果函数的参数和class T有关,则使用实例化区里的函数,否则使用声明区里的函数。在上面的例子中,sr0.invariant()中foo函数的参数是_val,而_val是int类型变量,于是和T无关,于是实际被调用的将是声明区里的double foo(double),即使foo的参数是double而不是int。而在sr0.type_independent()语句中,type_independent实例实际上是:

int _member;
.... int type_dependent()
{
return foo(_memble);
}

foo的参数是_member,而_member的类型依赖于T,因此使用实例化区的foo。这样意味着,编译器要维护两个scope context:

1  模版声明区,专注于一般的template class

2  模版实例化区,专注于特定的template class object

至于两个foo的返回值?Oh nonono,和函数的重载一样,编译器根本不管返回值这个东西(但是为什么不管呢?),编译器只在乎函数原型(函数名、参数列表)。个人猜测,这是因为函数的返回值往往用来赋值给某变量(除非仅仅是为了生成个临时变量),然而这个变量的类型的范围可就广了,比如对于POD,int可以赋值给char、short、float等多种类型的变量,即使会因为长度不懂而被截断也不管;而对于类,基类和派生类之间在类型转换符static_cast的帮助下也可以相互赋值。那么对于int foo()和double foo(),如果调用者这么写:char c = (char)foo(),编译器当然就不知道调用者到底是几个意思了。换句话说,不到函数调用语句看看,就无法分辨不同的返回值之间的最终的区别。

总而言之,编译器对模版做了一件事:按需实例化。而为了实现这个按需实例化,里面的办法却并不简单。

【对象模型】C++模版的编译链接过程——编译器真的会检查所有tocken层面的错误么?的更多相关文章

  1. [转]C++编译链接过程详解

    C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接.编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程.链接是把目标文件.操作 ...

  2. C-从源文件到可执行文件的详细编译链接过程

    一直用windows一键搞定, 没有去了解详细的编译链接过程, 今天看了一篇文章, 顺便实验和记录在Linux下逐步生成的步骤. 预处理: 执行#include, #define, #if, #ifd ...

  3. 转:C语言的编译链接过程的介绍

    11:42:30 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接.编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程.链接 ...

  4. GCC编译链接过程

    编译链接过程 代码 #cat main.c #include <stdio.h> int add(int x, int y); int sub(int x, int y); int mul ...

  5. C/C++编译链接过程详解

    有些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错误信息不能定位到某 ...

  6. 转:从编译链接过程解析static函数的用法

    关于static函数的用法 就像我们熟知的那样,变量可以分全局的和局部的,函数也可以分全局的和局部的. 比如说,在一个工程的common.h中定义了一个全局变量 int test;那么在整个工程的作用 ...

  7. Delphi编译/链接过程

    下面展示了Delphi是怎样编译源文件,并且把它们链接起来,最终形成可执行文件. 当Delphi编译项目(Project)时,将编译项目源文件.窗体单元和其他相关单元,在这个过程中将会发生好几件事情: ...

  8. Delphi 编译/链接过程

     

  9. 嵌入式C语言自我修养 09:链接过程中的强符号和弱符号

    9.1 属性声明:weak GNU C 通过 __atttribute__ 声明weak属性,可以将一个强符号转换为弱符号. 使用方法如下. void __attribute__((weak)) fu ...

随机推荐

  1. LoadCursor 函数

    从可执行文件中载入指定的光标资源,加载到指定的应用实例中 ? 1 2 3 4 5 HCURSOR WINAPI LoadCursor(    _In_opt_ HINSTANCE hInstance, ...

  2. jQuery插件Skippr实现焦点图

    史上效果最好的焦点图幻灯片jQuery插件Skippr,轻量级插件.响应式布局插件,强大的参数自定义 配置,可自定义切换速度.切换方式.是否显示左右箭头.是否自动播放.自动播放间隔时间等配置 参数,调 ...

  3. ASP.NET中实现页面间的参数传递

    ASP.NET中实现页面间的参数传递   编写人:CC阿爸 2013-10-27 l  近来在做泛微OA与公司自行开发的系统集成登录的问题.在研究泛微页面间传递参为参数,综合得了解了一下现行页面间传参 ...

  4. Vue.js学习 Item1 --快速入门

    我们以 Vue 数据绑定的快速导览开始.如果你对高级概述更感兴趣,可查看这篇博文. 尝试 Vue.js 最简单的方法是使用 JSFiddle Hello World 例子.在浏览器新标签页中打开它,跟 ...

  5. Silverlight动态设置WCF服务Endpoint

    2013-02-02 05:57 by jv9, 1763 阅读, 3 评论, 收藏, 编辑 去年12月收到一位朋友的邮件,咨询Silverlight使用WCF服务,应用部署后一直无法访问的问题,通过 ...

  6. Azkaban遇到的坑-installation Failed.Error chunking

    在使用azkaban做spark作业调度时,在上传zip包时报installation Failed.Error chunking错误,原来是于我们所编写的应用会上传到 MySQL 存储,过大的zip ...

  7. PHP 文件上传服务端及客户端配置参数说明

    文件上传服务器端配置: ·file_uploads = On, 支持HTTP上传 ·upload_tmp_dir = , 临时文件保存的目录 ·upload_max_filesize=2M, 允许上传 ...

  8. phpQuery采集微信公众号文章乱码

    终于找到解决方案了,这是一个值得庆祝的事情.... 原来是因为微信在源码中加入了防采集代码<!--headTrap<body></body><head>< ...

  9. SQLite数据库管理的相关命令

    1.创建数据库 启动命令行,通过输入如下命令打开Shell模式的CLP: sqlite3 test.db 虽然我们提供了数据库名称,但如果该数据库不存在,SQLite实际上就未创建该数据库,直到在数据 ...

  10. jQuery学习笔记(1)

    设置和获取HTML,文本和值 val()方法: 1.单选框 <html xmlns="http://www.w3.org/1999/xhtml"> <head r ...