查询网站mx记录手机网站建设方案doc
查询网站mx记录,手机网站建设方案doc,开个人网站怎么赚钱,免费的活动策划网站一、模板编译的特点
模板的编译相对于普通编程的编译#xff0c;要复杂不少。比如一个模板函数#xff0c;在不同的编译单元被include#xff0c;那么会生成多个相同签名的函数#xff0c;这就需要编译器后期进行相关的去重处理。而且这种代码多了#xff0c;编译时#…一、模板编译的特点模板的编译相对于普通编程的编译要复杂不少。比如一个模板函数在不同的编译单元被include那么会生成多个相同签名的函数这就需要编译器后期进行相关的去重处理。而且这种代码多了编译时相关的编译部分体积也会变大也就是常说的代码膨胀。另外还需要处理ADL和CTAD前面都分析过等相关的细节直到链接时对相关函数的具体定位两阶段名称查找等等都相较于非模板代码编译需要更多的步骤和处理过程。这里重点分析一下为什么在模板编程中见到的模板代码都定义在头文件中而不是象普通的代码声明在头文件而定义在cpp中也就是分离编译的问题。二、为什么不支持模板分离编译那么为什么不支持模板的分离编译呢这就需要从C的编译过程和模板的特点说起。在C编译的过程中普通的代码是在一个个的CPP文件做为独立的编译单元进行编译的然后在链接阶段进行相关函数等符号的链接。但是模板有一个特点即经常提到的延迟加载即不调用的情况下一般不会生成实例也就无法生成相关的符号链接。那么如果去链接就会报链接错误。这也是为什么模板编译时很多问题都体现在了链接错误上的一个重要原因这里并未严格区分编译和链接统一处理为编译。大多数情况下模板的实例化都是隐式实现的。虽然这样做更符合传统的开发风格以及相关的技术处理但这也从某种角度加强了开发者对模板编译与普通代码编译的过程从而导致分离编译时的错误情况产生。回想一下前面对模板的分析模板就是一个“定义代码的空架子”它本身对编译器是没有多大作用的只有将这个“空架子”填充上真实的数据类型后即实例化后编译器才会真正的将其作为可处理的代码进行编译。有的资料上说模板是一种“蓝图”不过觉得说是一个空架子反而更简单。下面看一个简单的例子//h #ifndef TEMPLATEDEMO_H #define TEMPLATEDEMO_H class TemplateDemo { public: TemplateDemo(); template typename T void getData(const T id); private: void insDemo(); }; #endif // TEMPLATEDEMO_H //cpp #include templatedemo.h #include iostream TemplateDemo::TemplateDemo() {} template typename T void TemplateDemo::getData(const T id) { std::cout this is get test! std::endl; } // void TemplateDemo::insDemo() { getData(100); }//注释则编译时链接有问题 //main.cpp #include templatedemo.h int main() { TemplateDemo t; int d 100; t.getData(d); return 0; }当注释打开和不打开的情况下可以发现编译后的汇编代码中就有getData是否出现的情况看下面的结果//注释 .file templatedemo.cpp .text .local _ZStL8__ioinit .comm _ZStL8__ioinit,1,1 .align 2 .globl _ZN12TemplateDemoC2Ev .type _ZN12TemplateDemoC2Ev, function _ZN12TemplateDemoC2Ev: .LFB1732: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movq %rdi, -8(%rbp) nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1732: .size _ZN12TemplateDemoC2Ev, .-_ZN12TemplateDemoC2Ev .globl _ZN12TemplateDemoC1Ev .set _ZN12TemplateDemoC1Ev,_ZN12TemplateDemoC2Ev .type _Z41__static_initialization_and_destruction_0ii, function _Z41__static_initialization_and_destruction_0ii: .LFB2229: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl %edi, -4(%rbp) movl %esi, -8(%rbp) cmpl $1, -4(%rbp) jne .L4 cmpl $65535, -8(%rbp) jne .L4 leaq _ZStL8__ioinit(%rip), %rax movq %rax, %rdi call _ZNSt8ios_base4InitC1EvPLT leaq __dso_handle(%rip), %rax movq %rax, %rdx leaq _ZStL8__ioinit(%rip), %rax movq %rax, %rsi movq _ZNSt8ios_base4InitD1EvGOTPCREL(%rip), %rax movq %rax, %rdi call __cxa_atexitPLT .L4: nop leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE2229: .size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii .type _GLOBAL__sub_I__ZN12TemplateDemoC2Ev, function _GLOBAL__sub_I__ZN12TemplateDemoC2Ev: .LFB2230: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $65535, %esi movl $1, %edi call _Z41__static_initialization_and_destruction_0ii popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE2230: .size _GLOBAL__sub_I__ZN12TemplateDemoC2Ev, .-_GLOBAL__sub_I__ZN12TemplateDemoC2Ev .section .init_array,aw .align 8 .quad _GLOBAL__sub_I__ZN12TemplateDemoC2Ev .hidden __dso_handle .ident GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 .section .note.GNU-stack,,progbits .section .note.gnu.property,a .align 8 .long 1f - 0f .long 4f - 1f .long 5 0: .string GNU 1: .align 8 .long 0xc0000002 .long 3f - 2f 2: .long 0x3 3: .align 8 4: //未注释 .file templatedemo.cpp ..... .type _ZN12TemplateDemo7insDemoEv, function _ZN12TemplateDemo7insDemoEv: .LFB1735: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $32, %rsp movq %rdi, -24(%rbp) movq %fs:40, %rax movq %rax, -8(%rbp) xorl %eax, %eax movl $100, -12(%rbp) leaq -12(%rbp), %rdx movq -24(%rbp), %rax movq %rdx, %rsi movq %rax, %rdi call _ZN12TemplateDemo7getDataIiEEvRKT_ nop movq -8(%rbp), %rax subq %fs:40, %rax je .L3 call __stack_chk_failPLT .L3: leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1735: .size _ZN12TemplateDemo7insDemoEv, .-_ZN12TemplateDemo7insDemoEv .section .rodata .LC0: .string this is get test! .section .text._ZN12TemplateDemo7getDataIiEEvRKT_,axG,progbits,_ZN12TemplateDemo7getDataIiEEvRKT_,comdat .align 2 .weak _ZN12TemplateDemo7getDataIiEEvRKT_ .type _ZN12TemplateDemo7getDataIiEEvRKT_, function _ZN12TemplateDemo7getDataIiEEvRKT_: .LFB1996: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movq %rdi, -8(%rbp) movq %rsi, -16(%rbp) leaq .LC0(%rip), %rax movq %rax, %rsi leaq _ZSt4cout(%rip), %rax movq %rax, %rdi call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKcPLT movq _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_GOTPCREL(%rip), %rdx movq %rdx, %rsi movq %rax, %rdi call _ZNSolsEPFRSoS_EPLT nop leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1996: .size _ZN12TemplateDemo7getDataIiEEvRKT_, .-_ZN12TemplateDemo7getDataIiEEvRKT_ .text .type _Z41__static_initialization_and_destruction_0ii, function _Z41__static_initialization_and_destruction_0ii: .LFB2239: ......说明C有改名机制当然大家也可以看完全编译后的文件然后用readelf等命令查看类似的情况可以更好理解这种情况。C模板编译的最突出的特征就是两阶段查找和延迟实例化。这才是导致C模板分离编译问题的根源。当然这也恰恰是模板编程可以被利用的一些特点通过延迟加载等降低编译的时间提高效率。这里简单说明一下模板编译的两阶段查找。第一阶段用来处理定义时的不依赖的名称可以理解为普通函数的不包括参数的相关内容的查找和验证。比如调用了一个模板函数调用了一个普通函数如果找不到直接就报错第二阶段则是在实例化时模板代码已经可以看作是具体的“普通代码”了此时需要对依赖的相关名称进行确定包括参数、调用的模板函数等等此时会触发ADL。延迟加载比较简单就不再赘述了。三、可分离编译的情况虽然C不支持模板的分离编译但在实际的工程中可以看到有些情况是可以进行分离编译的。主要有以下几种情况普通类的模板函数 外部模板编译单元主要是分离编译的类文件内部的使用这种情况其实和不分离编译没有本质的区别在编译单元内可以看到模板相关的实现。特别是既然使用就会实例化。看下面的例子//hclass Demo{public:templatetypename Tvoidtest(constT);private:voidcall();intm_a0;};//cpptemplatetypename TvoidDemo::test(constTid){}voidDemo::call(){test(m_a);}显式实例化即使用模板的显式实例化这样等同于有了普通的代码。看下面的代码//在上面的cpp最后增加下面的显式实例化代码其它都不变templatevoidDemo::testint(constintid);外部模板外部模板的使用本质和显式声明没有太大的区别它主要是显式的让编译器不再生成过多的实例代码。比如仍然是上面的例子但在main函数调用前增加相关的外部模板声明externtemplatevoidTemplateDemo::getDataint(constint);intmain(){}C20模块的引入模块机制的引入并不能解决模板的分离编译但可以从逻辑上解决对头文件的依赖问题让模板编程更容易使用。四、分析总结从上面的分析来看只要能在一个编译单元中看到相关模板的实现就编译没有问题。或者说没有外部调用只在内部定义延迟加载也可以但这种情况意义不大。所谓分离编译与不分离的编译对编译器来说都必须能够找到相关的符号链接地址否则就是报错。这才是问题的重点。至于C中模板为什么不使用分离编译原因就在于两阶段查找和延迟加载导致链接问题。当然如果强行使用分离编译可能会引发更多的问题会付出更多的代价反而得不偿失。五、总结分离编译有分离编译的优点比如容易定位错误位置、共享模块等但对于模板来说可能不分离编译能更好的支持模板的特性这才是重点。正如反复提及的最合适的就是最好的这才是解决问题的态度。