我有一些模板代码,希望将其存储在CPP文件中,而不是内联在标头中。我知道只要您知道将使用哪种模板类型就可以做到。例如:

class foo
{
public:
    template <typename T>
    void do(const T& t);
};


.cpp文件

template <typename T>
void foo::do(const T& t)
{
    // Do something with t
}

template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);


注意最后两行-foo :: do模板函数仅与ints和std :: strings一起使用,因此这些定义意味着应用程序将链接。 hack还是可以与其他编译器/链接器一起使用?目前,我仅将此代码与VS2008一起使用,但希望将其移植到其他环境。

评论

我不知道这是可能的-一个有趣的把戏!知道这将有助于一些近期的任务-欢呼!

让我大吃一惊的是do作为标识符的用法:p

我在gcc上做了类似的事情,但仍在研究

这不是“ hack”,而是前向声明。这在语言标准中占有一席之地;所以是的,每个符合标准的编译器都允许使用它。

如果您有数十种方法怎么办?您可以只做模板类foo ;模板类foo ;在.cpp文件的末尾?

#1 楼

您可以通过在标题中定义模板或通过上面描述的方法来解决您描述的问题。

我建议从C ++ FAQ Lite中阅读以下几点:


为什么不能将模板类的定义与其声明分开,然后将其放入.cpp文件中?
如何避免模板函数出现链接器错误?有关模板链接器错误的C ++关键字导出帮助?

它们详细介绍了这些(和其他)模板问题。

评论


只是为了补充答案,引用的链接肯定会回答问题,即可以执行Rob的建议并使代码具有可移植性。

–ivotron
2011年5月1日21:46

您可以在答案本身中发布相关部分吗?为什么在SO上甚至允许这样的引用。我不知道该链接中要查找的内容,因为此后已发生很大的变化。

–身份
15年8月16日在21:57

#2 楼

对于此页面上的其他用户,我想知道用于显式模板专业化(或至少在VS2008中)的正确语法是什么(就像我一样),其语法如下。

.h文件中...

 template<typename T>
class foo
{
public:
    void bar(const T &t);
};
 


在您的.cpp文件中

template <class T>
void foo<T>::bar(const T &t)
{ }

// Explicit template instantiation
template class foo<int>;


评论


您的意思是“用于显式CLASS模板专业化”。在那种情况下,将覆盖模板化类具有的每个函数吗?

– 0x26res
13年2月21日在13:57

@Arthur似乎没有,我在头文件中保留了一些模板方法,而在cpp中大多数其他方法都可以正常工作。非常好的解决方案。

–user1633272
19-10-3在15:29

对于询问者,它们具有功能模板,而不是类模板。

–user253751
3月6日10:56

#3 楼

这段代码格式正确。您只需注意模板的定义在实例化时可见。要引用该标准,请参见第14.7.2.4节:



非导出函数模板,非导出成员函数模板或非导出成员函数或静态数据的定义类模板的成员应该出现在每个显式实例化了它的翻译单元中。


评论


非出口是什么意思?

–丹·尼森鲍姆(Dan Nissenbaum)
2014年6月12日19:29

@Dan仅在其编译单元内可见,而在其外部不可见。如果将多个编译单元链接在一起,则可以在它们之间使用导出的符号(并且对于模板,必须具有单个符号,或者至少具有统一的定义,否则会遇到UB)。

–康拉德·鲁道夫(Konrad Rudolph)
2014年6月12日19:32

谢谢。我认为所有功能(默认情况下)在编译单元外部都是可见的。如果我有两个编译单元a.cpp(定义函数a(){})和b.cpp(定义函数b(){a())),那么这将成功链接。如果我是对的,那么以上引用似乎不适用于典型情况……我在某处出错了吗?

–丹·尼森鲍姆(Dan Nissenbaum)
2014年6月12日19:46

@Dan Trivial反例:内联函数

–康拉德·鲁道夫(Konrad Rudolph)
2014年6月12日在20:02

@Dan函数模板是隐式内联的。原因是没有标准的C ++ ABI,很难/不可能定义否则会产生的影响。

–康拉德·鲁道夫(Konrad Rudolph)
2014年6月12日在21:41



#4 楼

您的示例是正确的,但不是很容易移植。还有一种更简洁的语法可以使用(由@ namespace-sid指出)。
假设模板化类是某些要共享的库的一部分。是否应编译模板版本类的其他版本?库维护者是否应该预期该类的所有可能的模板化用法? h文件
#pragma once

template <typename T>
class foo
{
public:
    void bar(const T& t);
};
// Include guards here, just in case
#pragma once

#include "foo.h"

template <typename T>
void foo::bar(const T& t)
{
    // Do something with t
}
// No include guards because this is file is compiled directly. I would consider adding "anti-guards" to make sure it's not included.

// Yes, we include the .cpp file
#include "foo.cpp"

template class foo<int>;

一个小警告是您需要告诉编译器编译foo.int.cpp而不是foo.cpp,因为编译后者没有任何作用;只是浪费了编译时间。
当然,您可以在第三个文件中拥有多个实现,也可以有多个实现文件,每种要使用的类型都有一个。
共享时,可以大大提高灵活性。
此设置还可以减少编译时间,因为您不必在每个翻译单元中重新编译相同的头文件。

评论


这买了什么?您仍然需要编辑foo-impl.cpp来添加新的专业化名称。

– MK。
17 Mar 22 '17 at 14:16

将实现细节(即foo.cpp中的定义)与实际编译时的版本(在foo-impl.cpp中)和声明(在foo.h中)分开。我不喜欢大多数C ++模板完全在头文件中定义。这与使用的每个类/名称空间/任何分组的c [pp] / h对的C / C ++标准相反。人们似乎仍在使用整体头文件,只是因为这种替代方法尚未广泛使用或未知。

– Cameron Tacklind
17 Mar 29 '17在20:06



@MK。首先,我将显式模板实例化放在源文件中定义的末尾,直到我需要在其他地方进行进一步实例化(例如,使用模拟作为模板类型的单元测试)。这种分离使我可以在外部添加更多实例化。此外,当我将原始实例保留为h / cpp对时,它仍然可以工作,尽管我不得不将原始实例化列表包含在一个include Guard中,但是我仍然可以像往常一样编译foo.cpp。我对C ++还是很陌生,并且想知道这种混合用法是否还有其他警告。

–第三水
18/12/28在7:02

我认为最好解耦foo.cpp和foo-impl.cpp。不要在foo-impl.cpp文件中#include“ foo.cpp”;而是添加声明extern模板类foo ;。到foo.cpp,以防止编译器在编译foo.cpp时实例化模板。确保生成系统生成两个.cpp文件,并将两个目标文件都传递给链接器。这有很多好处:a)在foo.cpp中很明显没有实例化; b)对foo.cpp的更改不需要重新编译foo-impl.cpp。

–Shmuel Levine
19年5月29日在14:17

这是一种很好的解决模板定义问题的方法,它同时兼顾了两个方面-头文件实现和常用类型的实例化。我对此设置所做的唯一更改是将foo.cpp重命名为foo_impl.h,将foo-impl.cpp重命名为foo.cpp。我还将为从foo.cpp到foo.h的实例化添加typedef,同样使用foo_int = foo ;。诀窍是为用户提供两个头接口供选择。当用户需要预定义的实例化时,他包括foo.h;当用户需要乱序的东西时,他包括foo_impl.h。

–更糟糕
19-10-30在20:04



#5 楼

在支持模板的任何地方都可以正常工作。显式模板实例化是C ++标准的一部分。

#6 楼

这绝对不是一个讨厌的技巧,但是要知道,对于给定模板要使用的每个类/类型,您都必须这样做(显式模板专门化)。如果有许多类型要求模板实例化,则.cpp文件中可能有很多行。要解决此问题,您可以在每个使用的项目中都有一个TemplateClassInst.cpp,以便您可以更好地控制要实例化的类型。显然,此解决方案并不完美(又称银弹),因为您可能最终会破坏ODR :)。

评论


您确定它将打破ODR吗?如果TemplateClassInst.cpp中的实例化行引用的是相同的源文件(包含模板函数定义),难道不能保证不违反ODR,因为所有定义都相同(即使重复)吗?

–丹·尼森鲍姆(Dan Nissenbaum)
2014年6月12日19:36

拜托,什么是ODR?

–不可拆卸
18年6月22日在11:45

#7 楼

在最新标准中,有一个关键字(export)可以缓解此问题,但是除Comeau之外,我没有意识到它在任何编译器中都没有实现。

请参阅关于此的常见问题-Lite。

评论


AFAIK,每次他们解决最后一个问题时,出口就死定了,因为他们正面临越来越多的问题,使整体解决方案变得越来越复杂。而且,无论如何,“ export”关键字都将使您无法从CPP“导出”(无论如何仍是从H. Sutter的)。所以我说:别屏住呼吸...

–paercebal
08-09-22在16:25

为了实现导出,编译器仍然需要完整的模板定义。您所获得的只是以某种编译形式进行的。但是,实际上没有任何意义。

–赞·山猫
2012年3月12日16:56

...由于过于复杂以至于无法获得最大收益,因此已偏离标准。

–DevSolar
15年8月26日在13:04

#8 楼

这是定义模板功能的标准方法。我认为我阅读了三种定义模板的方法。也许是4。各有利弊。


在类定义中定义。我根本不喜欢这样,因为我认为类定义仅供参考,并且应该易于阅读。但是,在类中定义模板要比在外部定义复杂得多。并不是所有的模板声明都具有相同的复杂度。此方法还使模板成为真正的模板。
在相同的标头中,但在类外部定义模板。在大多数情况下,这是我的首选方式。它使您的类定义保持整洁,模板仍然是真实的模板。但是,它需要完整的模板命名,这可能很棘手。此外,您的代码对所有人开放。但是,如果您需要内联代码,则这是唯一的方法。您也可以通过在类定义的末尾创建一个.INL文件来实现此目的。
将header.h和Implementation.CPP包含在main.CPP中。我认为就是这样。您无需准备任何预实例化,它的行为就像一个真正的模板。我的问题是它不是自然的。通常,我们不包含并且希望包含源文件。我想因为您已包含源文件,所以可以内联模板函数。
最后一种方法是发布方法,它在源文件中定义模板,就像数字3一样。但是我们没有将源文件预先包含在模板中,而是将它们实例化为所需的模板。我对此方法没有问题,有时会派上用场。我们有一个大代码,无法从内联中受益,因此只需将其放入CPP文件中即可。而且,如果我们知道通用的实例化,就可以预定义它们。这使我们不必编写5到10遍基本相同的东西。这种方法的好处是保持我们的代码专有。但是我不建议在CPP文件中放置经常使用的微小函数。因为这会降低您的库的性能。

注意,我不清楚膨胀的obj文件的后果。

#9 楼

是的,这是进行专门化显式实例化的标准方法。如您所述,您无法使用其他类型实例化此模板。

编辑:基于注释进行了更正。

评论


对术语保持谨慎是一种“显式实例化”。

–理查德·科登(Richard Corden)
08-09-22在16:19

#10 楼

让我们举一个例子,由于某种原因,您想要一个模板类:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}


如果您使用Visual Studio编译此代码,那么它可以直接使用。
gcc会产生链接器错误(如果从多个.cpp文件中使用了相同的头文件):但是然后您需要这样声明类-

error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here


然后.cpp将如下所示:头文件中没有最后两行-gcc可以正常工作,但是Visual Studio会产生错误:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test();

template <>
void DemoT<bool>::test();

// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;


如果您需要想要通过.dll导出公开功能,但这仅适用于Windows平台-因此test_template.h可能如下所示:

//test_template.cpp:
#include "test_template.h"

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}



但是这会使链接器更加头痛,因此建议使用前面的示例如果不导出.dll函数。

#11 楼

是时候更新了!创建一个内联文件(.inl或其他文件),然后简单地在其中复制所有定义。确保在每个功能上方添加模板(template <typename T, ...>)。现在,与在内联文件中不包括头文件相反,您执行相反的操作。在类声明后添加内联文件(#include "file.inl")。我认为没有立即出现的弊端。

评论


直接的缺点是,它与直接在标头中定义模板函数基本相同。一旦#include“ file.inl”,预处理器将把file.inl的内容直接粘贴到头文件中。无论您想避免在标头中进行实现的原因为何,该解决方案都无法解决该问题。

–科迪·格雷♦
2013年6月25日下午3:38

-并且这意味着您在技术上不必要地负担着编写离线模板定义所需的所有冗长且费力的重复样板的工作。我明白了人们为什么要这样做-与非模板声明/定义实现最大的同等性,使接口声明看起来整洁等-但这并不总意味着麻烦。这是评估双方的权衡并选择最坏的情况。 ...直到名称空间类成为事物:O [请成为事物]

– underscore_d
16年8月2日在20:52



@Andrew似乎已经卡在委员会的管子里,尽管我认为我看到有人说这并非故意。我希望它已经成为C ++ 17。也许下个十年。

– underscore_d
16-09-14在10:05



@CodyGray:从技术上讲,这对于编译器确实是相同的,因此不会减少编译时间。我仍然认为这值得一提,并在我见过的许多项目中得到了实践。走这条路有助于将接口与定义分开,这是一个好习惯。在这种情况下,它对ABI兼容性等没有帮助,但可以简化对接口的阅读和理解。

– Kiloalphaindia
18年5月7日在9:29

#12 楼

以上都不对我有用,因此这是您如何解决的,我的班级只有1个方法模板化。.

.h > .cpp

class Model
{
    template <class T>
    void build(T* b, uint32_t number);
};


这避免了链接器错误,并且根本不需要调用TemporaryFunction

#13 楼

您所举的例子没有错。但是我必须说,我认为将函数定义存储在cpp文件中并不高效。我只了解需要分开函数的声明和定义。

当与显式类实例一起使用时,Boost Concept Check Library(BCCL)可以帮助您在cpp文件中生成模板函数代码。 />

评论


什么是低效的?

–科迪·格雷♦
13年6月25日,下午3:40