C ++显式模板定义 - 代码仍然是重复的

时间:2018-05-08 14:50:03

标签: c++ visual-c++

我有一些模板代码可以实现非常繁重的计算,但我只需要它用于浮点数和双精度数。目标是模板实例化只在一个编译单元中完成一次,而不是对每个文件重复。

我尝试按照以下Stackoverflow帖子中的提示进行操作:

和类似的重复问题。我想出了以下测试来说明问题:

A.H

#pragma once
#include <cmath>
template<typename T>
struct A
{
    static T foo(T a, T b)
    {
        //do some heavy computations
        T v1 = pow(a, b);
        return pow(v1, b);
    }
};

//explicit template instantiations, the declaration
extern template struct A<float>;
extern template struct A<double>;

A.cpp

#include "A.h"
//explicit template instantiations, the definition
template struct A<float>;
template struct A<double>;

Main.cpp的

#include "A.h"
int main()
{
    //use A
    float result = A<float>::foo(0, 0);
    return (int)result; //return it so that it doesn't get optimized away
}

当我现在查看生成的.obj文件(dumpbin / DISASM)时,我得到以下输出:

A.OBJ

Dump of file A.obj

File Type: COFF OBJECT

?foo@?$A@M@@SAMMM@Z (public: static float __cdecl A<float>::foo(float,float)):
  0000000000000000: F3 0F 11 4C 24 10  movss       dword ptr [rsp+10h],xmm1
  0000000000000006: F3 0F 11 44 24 08  movss       dword ptr [rsp+8],xmm0
  000000000000000C: 55                 push        rbp
  000000000000000D: 57                 push        rdi
  000000000000000E: 48 81 EC 18 01 00  sub         rsp,118h
                    00
  0000000000000015: 48 8D 6C 24 30     lea         rbp,[rsp+30h]
  000000000000001A: 48 8B FC           mov         rdi,rsp
  000000000000001D: B9 46 00 00 00     mov         ecx,46h
  0000000000000022: B8 CC CC CC CC     mov         eax,0CCCCCCCCh
  0000000000000027: F3 AB              rep stos    dword ptr [rdi]
  0000000000000029: F3 0F 10 8D 08 01  movss       xmm1,dword ptr [rbp+108h]
                    00 00
  0000000000000031: F3 0F 10 85 00 01  movss       xmm0,dword ptr [rbp+100h]
                    00 00
  0000000000000039: E8 00 00 00 00     call        ?pow@@YAMMM@Z
  000000000000003E: F3 0F 11 45 04     movss       dword ptr [rbp+4],xmm0
  0000000000000043: F3 0F 10 8D 08 01  movss       xmm1,dword ptr [rbp+108h]
                    00 00
  000000000000004B: F3 0F 10 45 04     movss       xmm0,dword ptr [rbp+4]
  0000000000000050: E8 00 00 00 00     call        ?pow@@YAMMM@Z
  0000000000000055: 48 8D A5 E8 00 00  lea         rsp,[rbp+0E8h]
                    00
  000000000000005C: 5F                 pop         rdi
  000000000000005D: 5D                 pop         rbp
  000000000000005E: C3                 ret

?foo@?$A@N@@SANNN@Z (public: static double __cdecl A<double>::foo(double,double)):
  0000000000000000: F2 0F 11 4C 24 10  movsd       mmword ptr [rsp+10h],xmm1
  0000000000000006: F2 0F 11 44 24 08  movsd       mmword ptr [rsp+8],xmm0
  000000000000000C: 55                 push        rbp
  000000000000000D: 57                 push        rdi
  000000000000000E: 48 81 EC 18 01 00  sub         rsp,118h
                    00
  0000000000000015: 48 8D 6C 24 30     lea         rbp,[rsp+30h]
  000000000000001A: 48 8B FC           mov         rdi,rsp
  000000000000001D: B9 46 00 00 00     mov         ecx,46h
  0000000000000022: B8 CC CC CC CC     mov         eax,0CCCCCCCCh
  0000000000000027: F3 AB              rep stos    dword ptr [rdi]
  0000000000000029: F2 0F 10 8D 08 01  movsd       xmm1,mmword ptr [rbp+108h]
                    00 00
  0000000000000031: F2 0F 10 85 00 01  movsd       xmm0,mmword ptr [rbp+100h]
                    00 00
  0000000000000039: E8 00 00 00 00     call        pow
  000000000000003E: F2 0F 11 45 08     movsd       mmword ptr [rbp+8],xmm0
  0000000000000043: F2 0F 10 8D 08 01  movsd       xmm1,mmword ptr [rbp+108h]
                    00 00
  000000000000004B: F2 0F 10 45 08     movsd       xmm0,mmword ptr [rbp+8]
  0000000000000050: E8 00 00 00 00     call        pow
  0000000000000055: 48 8D A5 E8 00 00  lea         rsp,[rbp+0E8h]
                    00
  000000000000005C: 5F                 pop         rdi
  000000000000005D: 5D                 pop         rbp
  000000000000005E: C3                 ret
....

Main.obj

Dump of file Main.obj

File Type: COFF OBJECT

?foo@?$A@M@@SAMMM@Z (public: static float __cdecl A<float>::foo(float,float)):
  0000000000000000: F3 0F 11 4C 24 10  movss       dword ptr [rsp+10h],xmm1
  0000000000000006: F3 0F 11 44 24 08  movss       dword ptr [rsp+8],xmm0
  000000000000000C: 55                 push        rbp
  000000000000000D: 57                 push        rdi
  000000000000000E: 48 81 EC 18 01 00  sub         rsp,118h
                    00
  0000000000000015: 48 8D 6C 24 30     lea         rbp,[rsp+30h]
  000000000000001A: 48 8B FC           mov         rdi,rsp
  000000000000001D: B9 46 00 00 00     mov         ecx,46h
  0000000000000022: B8 CC CC CC CC     mov         eax,0CCCCCCCCh
  0000000000000027: F3 AB              rep stos    dword ptr [rdi]
  0000000000000029: F3 0F 10 8D 08 01  movss       xmm1,dword ptr [rbp+108h]
                    00 00
  0000000000000031: F3 0F 10 85 00 01  movss       xmm0,dword ptr [rbp+100h]
                    00 00
  0000000000000039: E8 00 00 00 00     call        ?pow@@YAMMM@Z
  000000000000003E: F3 0F 11 45 04     movss       dword ptr [rbp+4],xmm0
  0000000000000043: F3 0F 10 8D 08 01  movss       xmm1,dword ptr [rbp+108h]
                    00 00
  000000000000004B: F3 0F 10 45 04     movss       xmm0,dword ptr [rbp+4]
  0000000000000050: E8 00 00 00 00     call        ?pow@@YAMMM@Z
  0000000000000055: 48 8D A5 E8 00 00  lea         rsp,[rbp+0E8h]
                    00
  000000000000005C: 5F                 pop         rdi
  000000000000005D: 5D                 pop         rbp
  000000000000005E: C3                 ret
....

A::foo按预期在A.obj中实例化。但是代码也会再次输入Main.obj,完全忽略了extern关键字。

如何告诉编译器(Visual Studio 2017,发布模式)不要内联方法,而是使用A.obj中的版本?

1 个答案:

答案 0 :(得分:3)

您可以使用__declspec(noinline)执行此操作。

但内联版本可能会更快。如果您担心二进制文件大小,您的.exe文件将只有该函数的单个实例。来自A.obj的代码未使用,并且在死代码消除步骤中将被链接器丢弃。

更新:将此信息放入A.h:

static __declspec( noinline ) T foo( T a, T b )
{
    //do some heavy computations
    T v1 = pow( a, b );
    return pow( v1, b );
}

我使用Visual C ++ 2017 15.6.7版本32和64位构建,对于这两个平台,Main.cpp编译为:

; Line 5
    call    ?foo@?$A@M@@SAMMM@Z         ; A<float>::foo
; Line 6
    cvttss2si eax, xmm0

但是,如果你这样做是为了减少编译时间,我不确定noinline会有所帮助。而是从A.h(离开声明)中删除函数体,将其移动到A.cpp中。理想情况下,还要从A.h中删除特征头(或保留最小的定义数据结构的头),并将特征头包含在A.cpp中。