我一直在做一些设计模板代码的阅读有一个问题。与将代码设计为模板相关的问题的大多数解决方案似乎都是:
例如:
// 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 :(得分: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
标题末尾的模板实现来影响模板的界面和实现的分离是很常见的,但是你如何做这三个问题:
#include "foo.cc"
应该里面包含警卫,而不是在外面。通过这种方式,用户无需执行额外的工作。你所做的就是#include
标题,你已经完成了。你没有编译实现。
答案 4 :(得分:0)
由于您在foo.cc
中加入了foo.h
,因此您可以将所有代码放入foo.h
并删除{{{}},从而简化您的生活。 1}}。将代码分成两部分没有任何好处。
答案 5 :(得分:0)
在c ++ 11中不推荐使用export关键字。因此,您将最终使用已弃用的代码。你把你的定义放在头文件中。