2010-09-11 96 views
5

学习C++,来到函数模板。该章提到了模板专业化。C++ - 函数模板专门化的目的是什么?何时使用它?

  1. template <> void foo<int>(int);

  2. void foo(int);

为什么专注时,你可以使用第二个?我认为模板是普遍化的。当您只需使用常规功能时,为特定数据类型专门设定功能有什么意义?

显然,模板专门化存在的原因。什么时候应该使用它?我读过Sutter的"Why not Specialize..."文章,但我需要更多的外行人版本,因为我只是在学习这些东西。

+0

也许带上地址:'&foo vs(void(...))foo'或专门的返回类型? – Anycorn 2010-09-11 20:41:19

+0

[功能模板专业化的重要性和必要性]的可能的副本(http://stackoverflow.com/questions/2197141/function-template-specialization-importance-and-necessity) – jamesdlin 2010-09-11 20:49:02

回答

11

主要区别在于,第一种情况是您为编译器提供了特定类型的实现,而第二种情况是您提供了不相关的非模板函数。

如果您总是让编译器推断这些类型,编译器会优先选择非模板函数,而编译器将调用free函数而不是模板,因此提供了一个与非模板化函数匹配的模板函数在大多数情况下,参数将具有相同的专业化效果。

在另一方面,如果在任何地方,你提供的模板参数(而不是让编译器推断),那么它只会调用通用模板,可能产生意想不到的结果:

template <typename T> void f(T) { 
    std::cout << "generic" << std::endl; 
} 
void f(int) { 
    std::cout << "f(int)" << std::endl; 
} 
int main() { 
    int x = 0; 
    double d = 0.0; 
    f(d); // generic 
    f(x); // f(int) 
    f<int>(x); // generic !! maybe not what you want 
    f<int>(d); // generic (same as above) 
} 

如果您已经为该模板的int提供了专门化,最后两个调用将调用该专用而不是通用模板。

+1

关于'f '事情的好处。 – 2010-09-11 20:41:17

+0

还有几个不同之处。过度使用限定名称不能很好地发挥作用,除非在同一范围内声明。扩展标准库函数需要专业化,而不是过载。随着更多的过载,ADL的浮躁往往会增加。 – Potatoswatter 2010-09-11 22:07:48

+0

虽然如果你的接口声明有一个你可以调用的函数(-template)'f',那么你不能依赖类似'f '这样的东西来工作,并且你不需要它,因为模板无论如何,论据是推论。扩展标准库的功能可以通过ADL很好地完成(就像使用'std :: swap'),而通常不可能通过在名称空间“std”中进行专门化来实现(例如,您无法将其专门化为模板化参数 - 将需要部分专业化)。基于范围的for循环完全基于ADL。 – 2010-09-11 22:27:13

4

我个人认为没有专门的功能模板的好处。通过不同的函数模板或非模板函数重载它可以说是优越的,因为它的处理更直观,并且总体上更强大(通过重载模板有效地实现了模板的部分特化,即使在技术上它被称为部分排序)。

Herb Sutter写了一篇文章Why not specialize function templates?,他不鼓励使用特殊函数模板,而是倾向于重载或编写函数模板,以便它们只是转发到类模板的静态函数,并改为专用类模板。

+0

由于函数模板speciailizations不参与重载过程,是'功能模板专业化'的好处,因为它加快了重载解决方案 – Chubsdad 2010-09-12 02:24:28

+0

@Chubsdad:我永远不会试图优化编译时间。应该以更高的理由作出决定,而不是编译器处理代码的时间。 – 2010-09-12 10:46:35

+0

您应该尝试优化编译器本身:) – 2010-09-12 11:43:17

0

我觉得这很重要。你可以像使用虚拟方法一样使用它。虚拟方法没有意义,除非其中一些是专门的。我用它来区分简单类型(int,short,float)和对象,对象指针和对象引用。 一个例子是通过调用对象serialize/unserialize方法处理对象的serialize/unserialize方法,而简单类型应该直接写入流。

+0

虚拟方法不能作为模板。 – Potatoswatter 2010-09-11 21:30:06

+0

@Potato:我认为他正在画一个平行线。如果你声明一个虚函数,但是没有派生类超载它,虚拟类型就会浪费。可以肯定的是,它并不是一个很好的平行线。 – 2010-09-11 22:55:00

-1

模板特殊化的一种情况是不能用于重载的情况,即模板元编程。以下是库中的实际代码,它在编译时提供一些服务。

namespace internal{namespace os{ 
    template <class Os> std::ostream& get(); 

    struct stdout{}; 
    struct stderr{}; 

    template <> inline std::ostream& get<stdout>() { return std::cout; } 
    template <> inline std::ostream& get<stderr>() { return std::cerr; } 
}} 

// define a specialization for os::get() 
#define DEFINE_FILE(ofs_name,filename)\ 
    namespace internal{namespace os{      \ 
     struct ofs_name{         \ 
      std::ofstream ofs;        \ 
      ofs_name(){ ofs.open(filename);}      \ 
      ~ofs_name(){ ofs.close(); delete this; }     \ 
     };           \ 
     template <> inline std::ostream& get<ofs_name>(){ return (new ofs_name())->ofs; } \ 
    }}            \ 
    using internal::os::ofs_name; 
+0

这看起来不正确。 'stdout'是一个对象(它可以是一个宏,它几乎毁了一切),所以它不应该与参数类型'class Os'匹配。如果有的话,你需要'template < FILE * > std :: ostream&get();' – Potatoswatter 2010-09-11 22:05:25

+0

@Patatoswatter:它的内部(命名空间内部)。稍后,它将以适当的名称进入图书馆的公共(而非全局)名称空间。模板也不错。 – Daniel 2010-09-12 13:02:30

0

同名多个重载做类似事情。专业化做完全相同的事情,但在不同的类型。重载具有相同的名称,但可以在不同的作用域中定义。一个模板仅在一个范围内声明,专业化声明的位置是微不足道的(尽管它必须位于封闭名称空间的范围内)。

例如,如果你扩展std::swap支持你的类型,必须通过专业化这样做,因为功能被命名为std::swap,不能简单地swap,并在<algorithm>的功能将是非常合适的专门称呼其为::std::swap(a, b);。同样,对于可能在名称空间中混叠的任何名称:一旦您限定名称,调用函数可能会变得“更难”。

范围问题进一步被argument-dependent lookup混淆。通常可能会发现重载,因为它定义在它的一个参数的类型附近。 (例如,作为静态成员函数。)这与模板专业化的发现方式完全不同,只需查找模板名称,然后在选择模板作为目标后查找显式专业化的通话。

ADL的规则是标准中最混乱的部分,所以我更愿意明确地专注于避免依赖它的原则。

+0

那么,正式的重载确实只发生在一个范围内。如果两个函数声明在不同的作用域中,那么它们不会每个都超载。但我想说的实际观点是 - 假设你有一个'template class Container {...};' - 你想如何专门化'std :: swap'来处理那个容器?这是不可能的,你很可能会用ADL做到这一点。因为这个原因,泛型函数不会只调用'std :: swap',因为这将是一个完全蹩脚的实现质量。 – 2010-09-12 01:51:47

+1

我认为一般模式是通过编写'{using std :: swap;交换(a,b); }'它可以通过ADL和'std :: swap'的特化找到两个声明。我觉得过载更容易。每个人都应该这样做,因为他认为合适:) – 2010-09-12 02:01:35

+0

@Johannes:你有一个可行的解决方案,但它不是标准指定的。关键是为'''找到的类实现'swap'。 (是的,只有“最好的”范围被用来找到一个匹配......但也是一个挫折的来源!有一个明确的优先倒置,因为用户可能不会看到作为最重要因素的范围特定细节。) – Potatoswatter 2010-09-12 02:06:24