MSVC8 对c++模板的支持,基本上遵循了c++标准的规定,不过在某些细节之处仍然超越了c++标准,多做了一些工作,从而一定程度上减轻了程序员编程的负担。但是这种简化,是一把双刃剑---当你使用MSVC8编程的时候你会感觉轻松而简便,你不知不觉中就陷入了MS的陷阱---当你需要把代码移植到使用 gcc的*nix上时候,你将遇到很多头疼的问题,甚至不得不放弃,从而困在微软的小世界里面。为了防患于未燃,我们应该在编码之前,就知道如何编写可移植的c++模板代码,不要使用MSVC8的扩展。我把我工作中学到的经验列出来,供大家参考,这样大家就不会犯我曾经犯过的错误。要知道,为了修正这些不可移植的代码,我用去了整整一个星期。我当时用英文记录的,不想翻译过来了,不过我会加一些中文说明。
1. If a data member or member function is inherited from a class template, use "this->member = ***;" or "this->member_function();" to access this member, rather than directly use "member = ***;" or "member_function();".
这条规则背后的原因是一个叫做"two phase lookup"的模板实现机制,gcc,icpc (intel c++ compiler) 等遵循了这个标准,因此如果在派生类(模板)中不加 this-> 直接访问基类模板中的成员,编译器会报错,认为找不到这个名字。当你加上this->后,编译器会到基类中查找这个名字。MSVC8 不遵循这个规则,所以在vc中你可以不加this->并仍然可以使用基类成员。
2. For nested template instantiation i.e. using a template class as a type parameter of another class template), use > > rather than >>. For example, use
"typedef my_outside_templ<ptint, ptint, my_inside_templ<ptint> > obj;"
rather than
"typedef my_outside_templ<ptint, ptint, my_inside_templ<ptint>> obj;"
Note the space between the two > characters.
c++标准规定,两个模板参数列表的右括号'>' 之间需要有一个空格,左括号'<'则不需要。至于为什么这样做我也不知道,可能是解析的时候有歧义吧,就像必须使用typename来取出类模板里面的类型一样。同样,MSVC中也不需要加这个空格。
3. Try your best to avoid circular inclusion, msvc8 can handle this much better, but not for gcc. So try best to use class forward declarations rather than inclusion, and also we need to design classes better.
这条规则不仅适用于模板代码,也适用于其他c++代码,因为对于有一定规模的c++项目,如果本可以使用类型声明的头文件中也直接包含定义了那个类型头文件,那么当某个被很多头文件包含的头文件有改动时候,会引起整个项目几乎所有文件都重新编译一遍,而且编译每个代码文件需要编译的代码也多了很多,这个时间开销是可怕的。
4. Always use typename to refer to a type inside a template.
比如,当我们使用 vector<int>类的iterator类型时候,我们应该这样写: typename vector<int>::iterator itr; 原因是,编译器无法知道vector<int>::iterator到底是指vector<int>类里面的一个函数或者静态成员,还是一个类型。这时编译器不做进一步的查找工作,直接默认认为它是一个函数名或者静态成员名称。MSVC8在有些情况下也受限于这个规定,有些时候却不。我想它经过了一些优化,比如当vector<int>已经被instantiated(我觉得 实例化不是一个很好的翻译,它指的是对象,所以保留英文名)的时候,编译器可以去那个模板类中寻找iterator的定义,进而知道它到底是一个类型还是一个函数/静态成员。但是gcc没有这么勤快,它直接按照标准的规定来理解vector<int>::iterator,于是报出编译错误。所以当你要取出 vector<int>中的iterator类型时候,需要写成 typename vector<int>::iterator,不可以省略"typename "
5. Don't declare self friend, by default self is self's friend; Don't declare a typedef'ed type friend, use the original type to declare friend.
按照c++标准,一个类是他自己的friend,所以我们不需要做这样的友元声明了。当我们确实声明一个类是自己friend时候,gcc认为这是一个错误(这也许是件好事,因为可能我们本想声明的是另一个类型是本类的friend);而MSVC8不报错。msvc8对友元声明有一个空缺,就是这样的声明在msvc8中无效,但是按照标准,它是有效的:
class A
{
template <typename T>
friend class MyTempl<T>;
// 其他类成员定义、声明
}; // A
在msvc8中,这个声明并不能使得MyTempl的成员函数访问到A的private/protected成员。
另外,关于friend,还有一个有趣的事情,就是c++中的friend声明的关系是单向的而不是双向的,也就是,如果类A声明类B是它的 friend,那么类B就可以访问类A的private/protected成员了,但是反过来却不行---类A仍然不能访问类B的private /protected成员,除非类B也声明类A是它的friend。
另外,在gcc中,如果我用typedef定义一个类型A的别名a,我不能声明: friend class a; 我只能声明: friend class A; msvc8没有这个限制。
6. Must use full type name like A<int> inside its derived class, like B, vc accepts bare db_map, but gcc require A<int> to compile.
例如,我们有类模板A:
template <typename T>
class A {/* 类A成员定义 */};
template <typename T>
class B {
B():A<T>(){} // 1
};
对于1行,gcc要求必须使用A<T>来指定模板类B的基类,而MSVC8中可以写成这样: B():A(){}, 这样写虽然方便,但是不符合逻辑,因为没有写清楚要使用A的那一个instantiation. 所以,不要偷懒,应该写成1行的样子。
7. Avoid using rtti to assist missing template features.
不同的编译器对RTTI 有不同程度的支持,依赖RTTI 的代码可移植性差。只要有合理的设计,是完全可以避免使用RTTI的;不得不使用RTTI的情况通常意味着你的类结构设计有问题。
8. gcc/g++ dose not recognize dos/win32 line end (/r/n), but it can compile the code, then when linking, there will be a lot of undefined reference errors. So do use unix line end (/n) before building. using dos2unix utility to do this.
9. Explicit specialization has to take place outside class body, in the namespace scope where the class definition resides, and leave the function declaration inside the class template; on windows the explicit spec has to be in the class body, so use a macro to switch.
比如我们有类模板A:
template <typename T>
class A {
void foo(T t);
}; // A
我们想对A::foo在T为int时做全特化,那么按照c++标准,我们应该写成:
template <typename T>
class A {
void foo(T t); // 一般声明
}; // A
template<typename T>
void A<T>::foo(T t){/* 一般定义 */}
template<>
void A<int>::foo(int t){/* 针对int类型的全特化定义 */}
而MSVC8不认识上述代码,它认识的代码是这样的:
template <typename T>
class A {
void foo(T t){/* 一般定义 */}
template<>
void foo(int t){/* 针对int类型的全特化定义 */}
}; // A
10. Function partial initialization
There are no partial specialisations of function templates, including member and non-member functions. And to partially specialise a member you need to first partially specialise the class template. But there are workarounds, as always it is to add another layey of indirection.
如果为了偏特化一个成员函数而不得不偏特化整个类,把90%的代码重复一遍,那将是极其可怕的。下面的代码片段是我在一个论坛上面找到的,我给它加了一些注释和说明,以便清楚地说明问题。我想它能够很清楚地解释如何简单地在不完全重定义偏特化类的情况下做成员函数偏特化。
#include <iostream>
#include <typeinfo>
struct example
{
template <typename U, typename V>
void patialy_specialized(U const &u, V const &v);
// A lot of data members and other member functions that we don't
// need partial specialization
};
We define a helper class example_patialy_specialized which simply is used to call the example::patialy_specialized, so it contains only one function to call it. Note example_patialy_specialized is a class template with identical list of arguments in patialy_specialized. And if the class "example" is itself a class template, example_patialy_specialized will need more template arguments for them.
/* helper class */
template < typename U, typename V >
struct example_patialy_specialized
{
static void apply(example *that, U const &u, V const &v)
{
std::cerr
<< "default version "
<< typeid( U ).name()
<< " - "
<< typeid( V ).name()
<< "/n";
}
};
/*
* Defenition of the template method we want to partially specialize, it
* simply indirects through the helper above or (importantly) one of
* its specializations.
*/
template < typename U, typename V >
inline void example::patialy_specialized( U const &u, V const &v)
{
return example_patialy_specialized< U, V >::apply( this, u, v );
}
/*
* Partial specialization of the helper class example_patialy_specialized,
* which has only one method, so we don't mind wholy rewrite the entire
* class template.
*/
template <typename U>
struct example_patialy_specialized<U, int>
{
static void apply(example * that, U const &arg, int)
{
std::cerr << "U int version " << typeid( U ).name() << "/n";
}
};
If in class "example" we have a few methods we want to partially specialize, simply add more methods for each of them in class template "example_patialy_specialized", and partially specialize everyone of them, if there are too many combinations, simply add more helper class like example_patialy_specialized.