MSVC环境下的template编译[纠正]

Edit on Github

一直以为自己对 template 的理解有问题,考虑到 template 是用到时再实例化,所以不能像一般的 class 那样写,比如这样:

// foo.h
class Foo
{
public:
    Foo();
    Type bar(Type&);
private:
    Type _val;
};
// foo.cpp
#include"foo.h"Foo::Foo()
: _val(0)
{};

Type Foo::bar(Type&val)
{
    _val = val;
    return _val;
}

这就是所谓的“分离编译模式”了,也就是类的定义和成员函数声明在头文件中,成员函数实现在另一个文件中。这样做的好处很多:

  1. 代码条理清晰,代码多了也碍眼。
  2. 包括该模块时,可以隐藏具体实现细节。
  3. 减少调用者包括的头文件

实现中用到的库文件、头文件,可以不被该模块包含者包含,如实现中用到了 assert,可以只在cpp文件中包括cassert文件。

  1. 优化编译速度

如只有cpp文件改动头文件没有改动时,调用者不用重新编译;而如果是“包含编译模式”(合在一起)的话,调用者也得重新编译了,也不知道写代码(特别是Debug)时,有什么时间都花在了编译上了。

上面的 class 封装成 template 就成了:

// foo.h
template <class> 
class Foo
{
public:
    Foo();
    Type bar(Type&);
private:
    Type _val;
}; 
// foo.cpp
template <class> 
Foo <type> 
::Foo()
: _val(0)
{};

template <class> 
Type Foo <type> 
::bar(Type&val)
{
    _val = val;
    return _val;
} 

一些资料上也提到了 template 的“分离编译模式”,大致有以下这几种:

  1. export 关键字法
  2. cpp 实现文件包含法
  3. 只在头文件中声明(伪分离)

1. export 关键字法

也就是实现文件中的函数定义都加上关键字 export,来声明一个可导出的函数模板。

// model2.h
// 分离模式:只提供模板声明
template <typename> 
Type min( Type t1, Type t2 ); 
// model2.c
// 模板定义
export template <typename> 
Type min( Type t1, Type t2 ) {/* . . . */} 

使用函数模板min()实例的程序只需在使用该实例之前包含这个头文件:

// user.c
#include"model2.h"int i, j;
double d = min( i, j ); // OK: 用法,需要一个实例

可惜对于类是没法用了。。。

2. cpp 实现文件包含法

也就是在头文件的底部包含cpp文件

// foo.h
#ifndef _FOO_H_
#define _FOO_H_

template <class> 
class Foo
{
public:
    Foo();
    Type bar(Type&);
private:
    Type _val;
};

#include"foo.cpp"#endif // _FOO_H_ 

3. 只在头文件中声明(伪分离)

这个就比较无语了。。。

// foo.h
#ifndef _FOO_H_
#define _FOO_H_
#include"foo.cpp"#endif // _FOO_H_
// foo.cpp
template <class> 
class Foo
{
public:
    Foo();
    Type bar(Type&);
private:
    Type _val;
};

template <class> 
Foo <type> 
::Foo()
: _val(0)
{};

template <class> 
Type Foo <type> 
::bar(Type&val)
{
    _val = val;
    return _val;
} 

总结:

经过试验,VS2008不支持分离编译模式(前两种),只支持“第三种”,可以忽略了。。

纠正:

2009-11-27 查看 OpenCV2.0 源码后纠正上面的总结,并得到VS2008的“分离编译模式”注意点,解决方法有两种:

  1. 到实现文件的后缀名改成属于头文件的后缀名,如h,hpp等,这样VS就不会编译实现文件了,也就是不生成obj文件。
  2. 或者,自定义实现文件的编译方式,右击实现文件-》属性中设置,快捷键ALt + F7,选择自定义,也就是默认不编译。

再次总结:

问题的真正原因往往会被表象所迷惑。

参考:

C++ Template学习笔记之函数模板(5)——模板编译模式 1.
请问在vs2005中类模板的编译采用的是哪种编译模型? 1.
C++ Template: Why do I get unresolved externals with my template code? 1.