• 文章
  • C++11 - 新特性 - 变参模板
发布
2012年5月3日 (最后更新:2012年5月31日)

C++11 - 新特性 - 变参模板

评分: 3.7/5 (386票)
*****

简介

在新的C++语言标准C++11出现之前,模板的使用在实现函数对象(仿函数)tuple功能等方面相当有限。使用早期C++标准实现这类东西通常需要重复编写相似的代码,并且不能忘记预处理器元编程。然而,得益于变参模板,使用模板编程新特性变得更简单更清晰更节省内存

虽然D编程语言也提供了变参模板,但本文只涵盖C++11标准提供的变参模板,因此阅读和理解本文不需要了解D编程语言的变参模板。但假设读者了解类模板和函数模板是什么,以及如何声明、定义和使用它们。

未找到图片。 什么是变参模板?

变参模板是一种可以接受任意数量、任意类型的模板参数的模板。类和函数都可以是变参的。这是一个变参类模板

1
2
template<typename... Arguments>
class VariadicTemplate;


以下任何一种创建此类模板实例的方式都是有效的

1
2
3
VariadicTemplate<double, float> instance;
VariadicTemplate<bool, unsigned short int, long> instance;
VariadicTemplate<char, std::vector<int>, std::string, std::string, std::vector<long long>> instance;


变参模板参数的数量也可以为零,因此以下

VariadicTemplate<> 实例;

在C++11中也是有效的。

但是,如果您创建如下模板

1
2
template<typename T, typename... Arguments>
class VariadicTemplate;


您必须至少设置一个类型作为模板参数(对于typename T),除非已经初始化了默认类型,如下声明

1
2
template<typename T = int, typename... Arguments>
class VariadicTemplate;

未找到图片。 语法 - 省略号运算符 (...):

省略号运算符 (...) 是C++中在不同上下文中使用的运算符。它的名字来源于C中的省略号机制。在这种机制下,程序员可以创建一个接受可变数量参数的函数。在C和C++中,最常利用这种机制的函数是C标准库中的printf函数。

int printf (const char* format, ... );

省略号机制也可以与预处理器的形式使用。接受可变数量参数的宏称为变参宏

#define VARIADIC_MACRO(...)

C++中,这个省略号运算符异常处理这一不同上下文中有了新的含义。该运算符用在try块之后的catch块中。

1
2
3
4
5
6
try{
    // Try block.
}
catch(...){
    // Catch block.
}


在这里,省略号运算符表示catch块接受来自try块的任何抛出的异常作为其参数,而不考虑类型。

在C++11中,变参模板为该运算符带来了又一个含义。该运算符的工作方式与前面所述的省略号机制有些类似,但更复杂一些。

1
2
template<typename... Arguments>
void SampleFunction(Arguments... parameters);


这是一个函数模板。变参模板参数的内容称为参数包。这些包随后在函数参数内部解包。例如,如果您调用前面的变参函数模板……

SampleFunction<int, int>(16, 24);

……一个等效的函数模板将是这样的

1
2
template<typename T, typename U>
void SampleFunction(T param1, U param2);

未找到图片。 语法 - sizeof... 运算符 (sizeof...):

与变参模板一起使用的另一个运算符是sizeof...运算符。与用于确定类型大小的sizeof运算符(例如 sizeof(int) 或 sizeof(double))不同,sizeof...运算符可用于确定传递给变参模板的类型的数量。这可以通过以下方式实现

1
2
3
4
5
template<typename... Arguments>
class VariadicTemplate{
private:
    static const unsigned short int size = sizeof...(Arguments);
};

未找到图片。 语法 - 两个省略号运算符一起使用 (......):

在某些情况下,可以将两个省略号运算符放在一起(......)。这两个运算符也可以分开写(写成 ... ...)

然而,最清晰的方式可能是用逗号将这两个运算符分开(..., ...)。无论带逗号还是不带逗号的写法都是可接受的。

这种语法可能出现在使用省略号机制的变参函数模板中。

1
2
3
4
template<typename... Arguments>
void SampleFunction(Arguments......){

}

如前所述,这两个省略号运算符组合在一起可以有不同的写法,所以以下示例

1
2
3
4
template<typename... Arguments>
void SampleFunction(Arguments... ...){

}

1
2
3
4
template<typename... Arguments>
void SampleFunction(Arguments..., ...){

}

也有效。

未找到图片。 作者观点与想法: 为了可读性,请使用最后一种方法来标记紧随的两个省略号运算符。之前的替代写法可能会令人困惑和/或繁琐。不过,有些人可能觉得这取决于个人口味。

未找到图片。 变参模板的用途 - 继承和初始化列表:

对于类而言,变参模板可用于继承初始化列表。利用变参模板的继承可以通过以下方式实现

1
2
template<typename... BaseClasses>
class VariadicTemplate : public BaseClasses...


另外,如果我们想在这个类中使用初始化列表来调用所有给定的基类作为模板参数的构造函数,我们必须这样做

1
2
3
4
5
6
7
template<typename... BaseClasses>
class VariadicTemplate : public BaseClasses...{
public:
    VariadicTemplate(BaseClasses&&... base_classes) : BaseClasses(base_classes)...{

    }
};


正如您所见,C++11在构造函数的参数列表中引入了一个新运算符——rvalue运算符(&&),它允许rvalue引用。本文无意涵盖此运算符的用法,但有关如何使用此运算符(以及rvalue引用的一般用法)的信息,请访问此链接
http://thbecker.net/articles/rvalue_references/section_01.html

未找到图片。 变参模板的用途 - 变参类模板特化:

与类模板一样,变参类模板也可以被特化。对于模板,特化是这样发生的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T>
class Template{
public:
    void SampleFunction(T param){

    }
};

template<>
class Template<int>{
public:
    void SampleFunction(int param){

    }
};


但对于变参模板,则是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename... Arguments>
class VariadicTemplate{
public:
    void SampleFunction(Arguments... params){

    }
};

template<>
class VariadicTemplate<double, int, long>{
public:
    void SampleFunction(double param1, int param2, long param3){

    }
};


未找到图片。 注意: 某些编译器可能尚不支持变参类模板特化,或者它们的实现可能有些不完整。

未找到图片。 相关链接:

如果您对查看利用变参模板的C++11标准类模板感兴趣,请查看上面提到的tuple,链接如下
http://www.cplusplus.com/reference/std/tuple/tuple/

变参模板可能派上用场的另一个领域是委托。如果您已经熟悉托管C++和/或C#,那么学习C++委托可能不是问题。您可能会在C++中找到它们的良好用途。

未找到图片。 结论:

模板一直是C++中的一个强大功能。现在,在引入变参模板之后,模板被证明更加强大。变参模板是实现委托和tuple的可靠解决方案。而且,与C风格的省略号机制相比,变参模板可以提供一种更类型安全的方式来替代它们。

更多信息 - 编译器支持

如果您不熟悉或不了解您的编译器功能支持情况,请访问此链接
http://wiki.apache.org/stdcxx/C++0xCompilerSupport

特别感谢

- 上帝
- 所有有用的资源(请参见下方)

资源

文本资源
http://digitalmars.com/d/1.0/variadic-function-templates.html
http://en.wikipedia.org/wiki/Variadic_template
http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
http://linuxprograms.wordpress.com/2008/03/07/c-ellipsis-operator-printf/
http://stackoverflow.com/questions/5625600/what-is-the-meaning-of-token/5625782#5625782
http://stackoverflow.com/questions/7767202/template-specialization-with-variadic-templates
http://www.cplusplus.com/doc/tutorial/exceptions/
http://www.devx.com/cplus/Article/41533
http://www.fredosaurus.com/notes-cpp/expressions/sizeof.html
http://www.generic-programming.org/~dgregor/cpp/variadic-templates.html