2011-12-01 19 views
1

我一直在阅读设计模板代码有一个问题。大部分的解决方案,与设计相关的代码为模板的问题似乎要么是:模板链接和分离.h和.cc文件

  1. 原型的认沽定义成头文件
  2. 使用出口关键字like this(这需要一个额外的编译器选项)
  3. 具体说明如何在.cc/.cpp文件中使用模板。

例如:

// File: foo_impl.cc 
// We're working with Class Foo 
#include "foo.cc" 

template class Foo <int>; 
template class Foo <string>; 
// etc. 

这些方法都显得非常有效。除非我错过了某些东西,否则它们似乎没有提供让用户简单导入头文件并将模板代码链接到(.cc文件中)而无需做额外工作的功能。我想知道是否人们可以看看我在用自己的代码做什么,并告诉我这些代码是否违反了某种最佳实践协议,或者是否可能导致我只是看不到的问题。这就是我一直在做...

在main.cc:

#include <iostream> 
#include "foo.h" 

using namespace std; 

int main (void) { 
    Foo <string> f ("hello world"); 
    string s = f.get(); 
    cout << s << endl; 

    return 0; 
} 

foo.h中:

#ifndef FOO_H 
#define FOO_H 

template <class T> 
class Foo { 
    public: 
     Foo (T); 
     T get(); 

    private: 
     T data; 
}; 

#endif 

#include "foo.cc" 

在foo.cc:

#ifndef FOO_CC 
#define FOO_CC 

#include "foo.h" 

template <class T> 
Foo :: Foo (T stuff) { 
    data = stuff; 
} 

template <class T> 
T Foo <T> :: get() { 
    return data; 
} 

#endif 

我已经能够用gcc 4.1.2中的所有警告来编译代码。谢谢!

+0

你只是想单独的文件就像我们正常的c + +文件,有定义和声明在单独的文件?或者您认为您可以避免将您的实现代码分享给其他将要将您的代码用作库的用户?你能详细说明你想达到什么吗? – havexz

+1

我只是想把我的模板代码看作是另一个.h和.cc文件。我希望有一个头文件,它可以很容易地读取通话内容,而不会使其膨胀。 – dorsalfinsalsa

+0

我其实也是这样做的,用'#ifdef _DEBUG template class Foo ; #endif',所以我可以强制它使用MSVC进行编译,并确保所有东西都没有错误地实例化。 –

回答

2

我个人更喜欢将声明(.h)放在与定义(您的.cc文件)不同的文件中。另外,我会避免在.h文件中包含.cc文件。 .h文件中的方法应该只是内联方法。

让我们说在你的例子中,你也有一个头文件(bar.h),它只是声明一个拥有Foo数据成员的类。

每次修改Foo类的定义时,都会导致包含bar.h的任何人的重新编译,即使是强硬的他们也不会关心Foo的定义。然而,bar.cpp可能是你真正实现的东西,该文件需要包括你的模板的实现。这在小型项目中看起来微不足道,但却成为大型项目中令人头疼的问题,因为这些大型项目无时无刻不重新编译文件。我看到有人把固态硬盘和Incredibuild放在可以通过简单的前向声明和更好的头部管理修复的东西上。

就我个人而言,我使用.imp.h来实现我的模板。包括cc文件或cpp文件对我来说似乎不太好。

例如(抱歉编译错误;))

// foo.h 
#ifndef foo_h 
#define foo_h 
template< typename T > 
struct Foo 
{ 
    Foo(T value); 
    void print(); 
    T _value;  
}; 
#endif 

//foo.imp.h 
#ifndef foo_imp_h 
#define foo_imp_h 
#include "foo.h" 
#include <iostream> 
template< typename T > 
Foo<T>::Foo(T value) : _value(value) {} 
void Foo<T>::print() { std::cout << _value << std::endl; } 
#endif 

// bar.h 
#ifndef bar_h 
#define bar_h 
#include "foo.h" 
struct Bar { 
    Foo<int> _intFoo; 
    Foo<double> _doubleFoo; 
    void print(); 
}; 
#endif 

// bar.cpp 
#include "bar.h" 
#include "foo.imp.h" 
void Bar::print() 
{ 
    _intFoo.print(); 
    _doubleFoo.print(); 
} 

// foobar.cpp 
#include "bar.h" 
void foobar() 
{ 
    Bar bar; 
    bar.print(); 
} 

只好foo的确定指标被包括在或通过foo.h中,bar.cpp和foobar.cpp会被重新编译。由于只有bar.cpp与Foo的实现有关,所以将Foo的定义和声明分成两个文件而没有foo.h,最后包含foo.imp.h为我重新编译了foobar.cpp。

这是在项目中始终发生的事情,可以通过遵循上面解释的.h/.imp.h规则很容易地避免。你从来没有像STL或boost这样的东西看到这个原因是因为你没有修改这些文件。它们是否在一个或两个文件中并不重要。但是在你自己的项目中,你将会不断地修改你的模板的定义,这就是你减少重新编译时间的方法。

如果您事先已经知道哪些类型实际上将与您的模板一起使用,那么您甚至不用打扰.imp.h文件。把所有东西放在一个.cpp文件中,最后做到这一点

// foo.cpp 
// Implementation goes here. 
// You might need to put something in front so that it gets exported from your DLL, 
// depening on the platform 
template class foo<int>; 
template class foo<double>; 
+0

根据@Seth Carnegie的回答,我决定将我的.cc文件重命名为.impl.h扩展名,以便它不会被编译。然后,我摆脱了.impl.h中的include语句,以便只包含一个include语句。 此外,每当我使用非模板类文件时,我总是只将头部包含到实现中,而不是以其他方式来避免此问题。 – dorsalfinsalsa

+0

哦,我陷入了困境。 'foobar.cpp'需要'Bar'完全定义,这需要'Foo'完全定义,但'foobar.cpp' TU并不包含'Foo'的实现,因为它没有调用任何那些功能。我在塞思卡耐基的回答中的评论是因为我想不出这样的例子。好! –

+0

同上。我认为这是我开发代码的方式,一旦我感觉它已经准备好进入“完成”阶段,我将切换它,这样只需要.h include。 – dorsalfinsalsa

0

既然你是包括foo.hfoo.cc,你会让你的生活将所有代码为foo.h和摆脱foo.cc简单。将代码分成两部分没有任何好处。

1

包含.cc文件是坏消息,并且破坏了从声明中分离实现的目的。

定义页眉模板:

#ifndef FOO_H 
#define FOO_H 

template <class T> 
class Foo { 
    public: 
     Foo (T); 
     T get(); 

    private: 
     T data; 
}; 

// implementation: 


template <class T> 
Foo :: Foo (T stuff) { 
    data = stuff; 
} 

template <class T> 
T Foo <T> :: get() { 
    return data; 
} 

#endif 

如果你真的喜欢2个文件,然后进行第二个中的.H了。将它命名为foo_impl.h或其他。

0

export关键字在C++ 11中被弃用。所以,你最终会得到不推荐的代码。你把你的定义放在头文件本身中。

+4

它不被弃用。它消失了。 [好吧,关键字仍然是保留的,但它不再有任何意义。导出功能完全删除。] –

+0

噢,好的谢谢你的更正。 – Jagannath

1

这是常见的#include荷兰国际集团在标题的末尾模板的实施影响的接口和执行模板形式的分离,但有三个问题,你是如何做的:

  1. 您将.h文件包含在.cc文件中。别;应该只有实现文件中的函数定义。
  2. 你的.cc文件不应该命名为.cc,它应该被命名。模板或类似的让人们知道,它不应该被编译的东西(如标题不应该被编译)
  3. foo.h中的#include "foo.cc"的包括警卫,不在外面。

完成这种方式后,没有额外的工作要完成。你所做的只是#include头,你就完成了。你不编译实现。

+0

谢谢!这绝对是从我开始的地方迈出的一步。在包含文件的注释中,我是否应该始终将.cc文件包含在.h文件中而不是其他文件中? 如果是这样,这是什么理由? – dorsalfinsalsa

+0

我强烈建议你不要在你的.h文件中包含你的templt。 .tmplt包含.h文件。其他.h文件通常只需要你的声明(除非需要你定义的.h文件中有一些内联代码)。 .cpps是需要定义的人,他们将包括.templt,其中包括.h文件自动为你 – fronsacqc

+1

@dorsalfinsalsa回复fronsacqcs评论和你的问题在同一时间,你可以轻松拥有其他.h文件包括.tmplt文件。无论如何,这并不重要,因为这种或那种.tmplt(和.h)的内容只会在每个.cc文件中结束一次。你可以选择,虽然我从来没有见过.tmplt包含.h,并且用户之前包含了.tmplt,但我认为这可能很奇怪,也很不直观。 –

2

让我们从.h和.cc文件的基本思想开始吧。在构建库时,这个想法是只共享你的头文件而不是你的实现(意思是.cc文件)。这也是OOP封装,抽象等基础知识来隐藏实现细节。

现在C++中的模板违反了这个原则,bcoz C++是一种编译语言。编译器在编译期间生成所有需要的代码。现在要坚持OOP,我们最终得到的脆弱模板在本质上不是100%通用的。

保持声明和定义分开(共享实现)

如果你只是想保持干净的东西,为了,那么您可以在另一头的实现文件。我认为它应该是头文件,因为这符合我们共享.h文件的基本约定,并且我们保持.cc文件不被共享(直到您共享代码本身)。这是文件的外观。

foo.h中

这是包括foo_impl.h简单的文件。

#ifndef FOO_H 
#define FOO_H 
template <class T> 
class Foo { 
    public: 
    Foo (T); 
    T get(); 
    private: 
    T data; 
}; 

#include "foo_impl.h" 
#endif 

foo_impl.h

这一个是从规范有点不同。这里我们没有看守头文件的内容。相反,如果直接包含foo_impl.h(在我们的例子中没有意义),我们会提出错误。

#ifndef FOO_H 
#error 'foo_impl.h' is not supposed to be included directly. Include 'foo.h' instead. 
#endif 

template <class T> 
Foo <T> :: Foo (T stuff) { 
    data = stuff; 
} 

template <class T> 
T Foo <T> :: get() { 
    return data; 
} 

现在,如果有人试图包括foo_impl.h直接将得到错误,如:

foo_impl.h:2:2: error: #error 'foo_impl.h' is not supposed to be included directly. Include 'foo.h' instead. 

优点:

  • 分离的关注,实施和声明在不同的文件。
  • 安全防范实施文件避免意外收录。
  • 用于包含的头文件不会与实现代码臃肿。

缺点:

  • 如上所述,必须共享的实现。

注意:对于不共享模板代码,我认为您已经知道您必须声明最终用户可以使用它的所有可能的类型。