C++11: 变参模板和std::tuple
变参模板Variadic Templates
变参模板(Variadic Templates)顾名思义就是参数类型和个数可以改变的模板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//定义 template<typename... Arguments> class VariadicTemplate; //实例化的方法 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; //参数个数甚至可以为0 VariadicTemplate<> instance; //变参模板函数 template<typename... Arguments> void SampleFunction(Arguments... parameters); //使用 SampleFunction<int, int>(16, 24); SampleFunction<std::string>("fun"); |
有人要问了这个省略号C语言里就有嘛,printf不就是不定参数的嘛。但是...但是,变参模板是类型安全的,而且它可以让类似功能的实现得到极大简化。这篇文章就 介绍了一个用变参模板实现的非常精巧的类型安全的printf, 还简要的说明了C++11引入这个特性的动机。我对模板元编程不甚了解,但是从大牛们用奇淫技巧实现的boost::mpl和boost::tuple来 模拟可变参数模板,不难看出这个功能对编写C++库的重要性。当然如果Concepts不被移出C++11标准,C++泛型能力会有翻天覆地的提高,不管怎样,C++11在语言层级增加了对变参模板支持,还是极大的增强了C++模板的抽象能力。
std::tuple
对于大多数程序员来说可能很少去编写模板库,但是新的可变参数的容器std::tuple大多数都会用到。tuple就是一个包含任意多个不同类型的数据成员的集合,就像一个增强版的std::pair。直接贴出一些用例,细节参照手册。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// tuple example #include <iostream> // std::cout #include <tuple> // std::tuple, std::get, std::tie, std::ignore int main () { std::tuple<int,char> foo (10,'x'); auto bar = std::make_tuple ("test", 3.1, 14, 'y'); std::get<2>(bar) = 100; // access element int myint; char mychar; std::tie (myint, mychar) = foo; // unpack elements std::tie (std::ignore, std::ignore, myint, mychar) = bar; // unpack (with ignore) mychar = std::get<3>(bar); std::get<0>(foo) = std::get<2>(bar); std::get<1>(foo) = mychar; std::cout << "foo contains: "; std::cout << std::get<0>(foo) << ' '; std::cout << std::get<1>(foo) << ' '; return 0; |
实际中应用
虽然变参模板无比拉风,但是平时编程时却不容易用到,tuple灵活强大,但是只用在函数返回值上,也未免大财小用,而且用多了会降低代码的可读 性。在项目中,有需求要测试触发signal的功能,需要一个slot的mock,signal 可能传递不同个数不同类型的参数,终于"以权谋私"用上了Variadic Templates和tuple。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
template<typename... Args> class SignalReceiverMock { public: SignalReceiverMock() : _slotCalled(false) {} void slot(Args... args) { _slotCalled = true; _arguments = std::make_tuple(args...); } bool slotCalled() { return _slotCalled; } bool argumentsPassedCorrectly(Args... args) { auto arguments = std::make_tuple(args...); return arguments == _arguments; } private: bool _slotCalled; std::tuple<Args...> _arguments; }; //在测试中使用 SignalReceiverMock<std::string, int> receiver1; SignalReceiverMock<> receiver2; Signal1 signal1("test", 0); // singal1 有传递两个参数 Signal2 signal2(); //signal2 不传递参数 signal1.connect(boost::bind(&SignalReceiverMock<std::string, int>::slot, *receiver1, _1, _2)); signal2.connect(boost::bind(&SignalReceiverMock<>::slot, *receiver2)); ....//触发signal的操作 testResult.asserTrue(receiver1.slotCalled() && receiver1.argumentsPassedCorrectly("test", 0)); testResult.asserTrue(receiver2.slotCalled()); |