C++编程实践—变参和完美转发的配合应用

一、变参

人类最大的满足就在于不满足。同样,在开发中,开发者总想着如果能够自动适配参数该有多好,不用一个个的写着各种不同的类型和不同数量的函数参数。
于是在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));  // 完美转发
}

完美转发对于模板函数中的参数传递应用来说,简直是一个完美的处理机制。它让开发者不用再考虑具体的数据类型等的限制,直接转走就可以了(当然,细节问题还是存在的,可以参看前面的“完美转发的异常情况”)。
完美转发能够解决不必要的参数拷贝,兼顾和通用性和灵活性。能够尽可能的提高性能。对于不同的值类型或者混合的值类型等都可以做到游刃有余。
不过,它会让代码看上去变得复杂一些,不容易为开发者维护屠理解。特别是在模板应用时,还有可能增加编译时间。

三、二者配合应用

变参可以和完美转发整合到一起,将二者的优势结合起来,实现很多具体的功能。通过上面的分析可以看到,变参与完美转发可以动态的将多个参数准确的传递到调用链中。这不正是很多开发者希望看到的情况么?
将这二者结合起来常见的应用场景包括

  1. 基础库
    比如对Tuple等的处理和实现,对基础库中的函数如empalce_back等的支持
  2. 设计模式
    在设计模式中,如工厂模式、代理模式以及策略模式等都可以应用
  3. 实现各种代理机制
    除了设计模式中的代理,还可以实现类似C#中的代理函数机制以及其它代理相关的参数控制
  4. 元编程
    这本来就是它们两个的最佳应用场景
  5. 其它
    其实还有很多方面都可以使用它们二者来解决问题,诸如一些复杂参数的构造、显示和日志处理等

四、分析

其实开发者都明白,对于单纯的基础数据类型的处理,效率的影响并没有多大。甚至可以说,有些情况下,完美转发可能反而影响性能的提高。但实际的应用场景是相当复杂的,大量的临时对象和复杂对象对于频繁应用的场景下,优势还是相当明显的。
但也需要注意,在实际的开发中,为了解决变参和完美转发的一些问题,可以增加一些限制条件。比如在模板的应用中可以使用一些概念或SFINAE技术(或者enable_if等都可以)。另外还要防止把右值完美转发后继续使用这些低级的错误问题。要对C++新标准有更多更深的理解,比如折叠表达式,否则在解决变参的折叠等处理时,可能遇到实际的问题。
那么在实际的应用中,如何能够更好的结合二者进行应用呢?可以从下面的几点来考虑:

  1. 如果需要与其它有类似情况的项目兼容则优先使用
  2. 增加对参数的约束特别是在模板编程中,引入最新概念等约束方法
  3. 如果设计通用库或框架,推荐结合二者应用
  4. 对性能和灵活性要求很高的场景下,推荐结合二者应用
  5. 团队的开发经验和新标准的引入程度高

在前面设计相关的文章中提到过,简单就是王道。在可能的情况下,还是推荐大家使用普通的重载等传统的方法来解决实际问题。当有上述的实际需求时,再使用二者结合来解决问题。解决问题的技术就是合适的技术,这才是永远的重点。

五、例程

新瓶装老酒,看一看原来的例子:

#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;
}

一个较为典型的工厂模式的实现,大家可以参考一下。

六、总结

开发者要善于从不同角度去看待各种问题,特别是组合的使用技术来解决问题。正如物理实验中,先加某个元素和后加某个元素可能直到的作用完全不一样。开发者也要善于利用这种多技术结合的机制,实现在实际项目中的工程创新,从而更好的解决问题,达到设计目的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值