.h和.cc文件的模板链接和分离

时间:2011-12-01 04:37:17

标签: c++ templates gcc

我一直在做一些设计模板代码的阅读有一个问题。与将代码设计为模板相关的问题的大多数解决方案似乎都是:

  1. 将原型的定义放入头文件
  2. 使用export关键字like this(需要额外的编译器选项)
  3. 具体说明如何在.cc / .cpp文件中使用模板。
  4. 例如:

    // 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中的所有警告编译代码。谢谢!

6 个答案:

答案 0 :(得分:2)

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

假设您的示例中还有一个头文件(bar.h),它只是声明一个具有Foo数据成员的类。

每次修改Foo类的定义时,都会导致重新编译包含bar.h的任何人,即使很难对Foo的定义也不太关心。但是,bar.cpp可能是您实际实现内容的地方,并且该文件需要包含模板的实现。这在小型项目中似乎微不足道,但在大型项目中成为令人头疼的问题,这些项目不断无故地重新编译文件。我已经看到人们抛出SSD和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 >;

答案 1 :(得分: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.

优点:

  • 关注点,实现和声明的分离在单独的文件中。
  • 安全防护实施文件,避免意外包含。
  • 用于包含的头文件没有使用实现代码膨胀。

CONS:

  • 如上所述,必须分享实施。

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

答案 2 :(得分: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或其他东西。

答案 3 :(得分:1)

通过#include标题末尾的模板实现来影响模板的界面和实现的分离是很常见的,但是你如何做这三个问题:

  1. 您在.cc文件中包含.h文件。别;在实现文件中应该只有函数定义。
  2. 你的.cc文件不应该被命名.cc,它应该被命名为.template或类似的东西,让人们知道它不应该被编译(比如不应该编译头文件)
  3. foo.h中的#include "foo.cc"应该里面包含警卫,而不是在外面。
  4. 通过这种方式,用户无需执行额外的工作。你所做的就是#include标题,你已经完成了。你没有编译实现。

答案 4 :(得分:0)

由于您在foo.cc中加入了foo.h,因此您可以将所有代码放入foo.h并删除{{{}},从而简化您的生活。 1}}。将代码分成两部分没有任何好处。

答案 5 :(得分:0)

在c ++ 11中不推荐使用export关键字。因此,您将最终使用已弃用的代码。你把你的定义放在头文件中。