一、变参
人类最大的满足就在于不满足。同样,在开发中,开发者总想着如果能够自动适配参数该有多好,不用一个个的写着各种不同的类型和不同数量的函数参数。
于是在C语言中就有了这种尝试,printf给了开发者一个变参的开端。但C++者的继任者们,拿到这个实现后,总是觉得它有点太低级。而且C++中还有更强大的模板编程,能不能够把变参扩展到更多的地方呢?有想法就是好的,于是,大佬们就把它实现了。类似于下面的代码:
template<typename... Args>
void setData(Args&&... args) {}
从上面的代码,可以看出,在C++中,变参可以很灵活的模板函数整合在一起。不过,看到上面的代码,就可以很自然的想到,模板类也可以是变参的。类似下面:
template<typename... Args>
class Data {};
当然,C++中也可以使用兼容C形式的变参函数实现,这里就不再赘述。
二、完美转发
Perfect Forwarding,完美转发,就是把万能引用和std::forward结合起来,将参数的原始属性完整的传递到参数应用中去。包括数据类型、左右值以及cv限定符等。
一般它的应用类似下面的代码:
template<typename T>
void transfer(T&& t) {
process(std::forward<T>(t)); // 完美转发
}
完美转发对于模板函数中的参数传递应用来说,简直是一个完美的处理机制。它让开发者不用再考虑具体的数据类型等的限制,直接转走就可以了(当然,细节问题还是存在的,可以参看前面的“完美转发的异常情况”)。
完美转发能够解决不必要的参数拷贝,兼顾和通用性和灵活性。能够尽可能的提高性能。对于不同的值类型或者混合的值类型等都可以做到游刃有余。
不过,它会让代码看上去变得复杂一些,不容易为开发者维护屠理解。特别是在模板应用时,还有可能增加编译时间。
三、二者配合应用
变参可以和完美转发整合到一起,将二者的优势结合起来,实现很多具体的功能。通过上面的分析可以看到,变参与完美转发可以动态的将多个参数准确的传递到调用链中。这不正是很多开发者希望看到的情况么?
将这二者结合起来常见的应用场景包括
- 基础库
比如对Tuple等的处理和实现,对基础库中的函数如empalce_back等的支持 - 设计模式
在设计模式中,如工厂模式、代理模式以及策略模式等都可以应用 - 实现各种代理机制
除了设计模式中的代理,还可以实现类似C#中的代理函数机制以及其它代理相关的参数控制 - 元编程
这本来就是它们两个的最佳应用场景 - 其它
其实还有很多方面都可以使用它们二者来解决问题,诸如一些复杂参数的构造、显示和日志处理等
四、分析
其实开发者都明白,对于单纯的基础数据类型的处理,效率的影响并没有多大。甚至可以说,有些情况下,完美转发可能反而影响性能的提高。但实际的应用场景是相当复杂的,大量的临时对象和复杂对象对于频繁应用的场景下,优势还是相当明显的。
但也需要注意,在实际的开发中,为了解决变参和完美转发的一些问题,可以增加一些限制条件。比如在模板的应用中可以使用一些概念或SFINAE技术(或者enable_if等都可以)。另外还要防止把右值完美转发后继续使用这些低级的错误问题。要对C++新标准有更多更深的理解,比如折叠表达式,否则在解决变参的折叠等处理时,可能遇到实际的问题。
那么在实际的应用中,如何能够更好的结合二者进行应用呢?可以从下面的几点来考虑:
- 如果需要与其它有类似情况的项目兼容则优先使用
- 增加对参数的约束特别是在模板编程中,引入最新概念等约束方法
- 如果设计通用库或框架,推荐结合二者应用
- 对性能和灵活性要求很高的场景下,推荐结合二者应用
- 团队的开发经验和新标准的引入程度高
在前面设计相关的文章中提到过,简单就是王道。在可能的情况下,还是推荐大家使用普通的重载等传统的方法来解决实际问题。当有上述的实际需求时,再使用二者结合来解决问题。解决问题的技术就是合适的技术,这才是永远的重点。
五、例程
新瓶装老酒,看一看原来的例子:
#include <utility>
#include <string>
#include <iostream>
class Example0
{
public:
Example0(int)
{
std::cout << "class instance example0!" << std::endl;
}
};
class Example1
{
public:
Example1(int, double)
{
std::cout << "class instance example1!" << std::endl;
}
};
struct Example2
{
Example2(int, double,std::string)
{
std::cout << "struct instance example3!" << std::endl;
}
};
template<typename T,typename... Args>
std::shared_ptr<T> Instance(Args&&... args)
{
return std::make_shared<T>(std::forward<Args>(args)...);
//return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
void TestInstance()
{
std::shared_ptr<Example0> p0 = Instance<Example0>(1);
std::shared_ptr<Example1> p1 = Instance<Example1>(1, 2.0);
std::shared_ptr<Example2> p2 = Instance<Example2>(1, 2,"Test");
}
int main()
{
TestInstance();
return 0;
}
一个较为典型的工厂模式的实现,大家可以参考一下。
六、总结
开发者要善于从不同角度去看待各种问题,特别是组合的使用技术来解决问题。正如物理实验中,先加某个元素和后加某个元素可能直到的作用完全不一样。开发者也要善于利用这种多技术结合的机制,实现在实际项目中的工程创新,从而更好的解决问题,达到设计目的。


904

被折叠的 条评论
为什么被折叠?



