跟我学c++中级篇——变参模板的应用

本文介绍变参模板在工厂类、代理委托及std::bind绑定中的应用。通过具体案例展示如何利用变参模板简化代码,提高代码灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、说明

很多技术学会了是学会了,但是转眼就会忘记。这也是很多技术被束之高阁的主要原因,没有应用场景。当然,有了应用场景不用,那是另外一回事。那么,一个技术的产生一定是从实际的应用场景中来,但是传播的过程中,可能反而让很多人忘记了这个应用场景是什么。所以今天就聊聊这个变参模板到底怎么用,给出一些在实际场景中模拟的例子,让人一看就明白的那类。这样,有了同样类似的场景,就可以考虑把这个变参模板应用上去。
在前面的分析中可以看到,一个变参模板类一般分为三部分,第一部分是模板类的前置声明用来声明一个可变参数模板类;然后是一般的展开形式,也就是通过偏特化来实现部分可展开的模板类,最后就是反复提到的终止定义模板类来保证递归等的结束判断,这里一般是一个特化的类或者是一个全实例化的类。
在实际的应用中,就可以把这块专门编写模板封装起来,供外部调用。

二、应用

下面举出三个场景来应用变参模板:

1、实现工厂类
如果在实际场景中,需要一个工厂模式(可不要说从来没遇到过用工厂模式),这个工厂模式有不确定多个参数和类型需要传递,怎么办?传统的方式可以写很多个重载函数,或者写一个最长,然后每个都有默认参数,根据实际传入的参数来决定到底采用哪种方式来创建实例。但是这样做显得代码臃肿且不易维护和扩展。当然,也可以抽象一个类(接口),子类来继承,通过动态创建不同的类来实现,但这就需要维护者去编写这个继承类,这或多或少也会让开发者感到有些不好理解,特别是如果这个父类被封装到一个DLL库中,看不到实际的代码时,更是如此。那么变参模板就很容易实现:
先看一下如果不使用变参模板:

template<typename T>
T* Instance()
{
    return new T();
}

template<typename T, typename T0>
T* Instance(T0 arg0)
{
    return new T(arg0);
}

template<typename T, typename T0, typename T1>
T* Instance(T0 arg0, T1 arg1)
{
    return new T(arg0, arg1);
}
//..............
//当然后面还可以写更多个参数类型的
#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;
    }
};
int main()
{
    Example0* pa = Instance<Example0>(3);
    Example1* pb = Instance<Example1>(3,2.0);
}

再看使用变参模板:

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

这样写比上面简洁多了,这就是标准进步的结果。

2、实现类似代理委托
如果有c#或者Java开发经验的就会知道,其中的委托delegate非常好用。各种函数都可以轻易的转发调度。那么,在c++中用变参模板就可以近似实现类似的功能,而且比之更灵活,看下面的例子:

template <class T, class R, typename... Args>
class  CppDelegate
{
public:
    CppDelegate(T* t, R(T::* func)(Args...)) :t_(t), func_(func) {}

    R operator()(Args&&... args)
    {
        return (t_->*func_)(std::forward<Args>(args) ...);
    }

private:
    T * t_;
    R(T::* func_)(Args...);//万能函数
};

template <class T, class R, typename... Args>
CppDelegate<T, R, Args...> CreateDelegate(T* t, R(T::* f)(Args...))
{
    return CppDelegate<T, R, Args...>(t, f);
}

class DgEx
{
public:
    void MyFunc0(int d) { std::cout << d << std::endl; }
    void MyFunc1(int d, std::string s) { std::cout << s << d << std::endl; }
    int MyFunc2(std::string s,int d) { std::cout << s << std::endl; return d; }
};

void TestDelegate()
{
    DgEx de;
    auto d0 = CreateDelegate(&de, &DgEx::MyFunc0);
    d0(16);
    auto d1 = CreateDelegate(&de, &DgEx::MyFunc1);
    d1(6, "display:");
    auto d2 = CreateDelegate(&de, &DgEx::MyFunc2);
    int r = d2("this is test!",3);
    std::cout << "return value is:" << r << std::endl;
}
void TestDelegate();

int main()
{
    TestDelegate();
    return 0;
}

这里面有一个万能函数,不知道还记不记得“C++变参函数实现三步曲之三C++11变参模板模式”文中就提到了这种方式。不过这个还是比较功能单一,大家可以在这个基础上进行扩展。

3、std::bind的变参绑定
这个在前面提到的文章中也有过描述,看一个例子:

#include <iostream>
#include <string>
#include <functional>

class MyBind
{
public:
    template<typename... T>
    void Call(T&... args)
    {
        {
            func_ = std::bind(&MyBind::Test<T&...>, this, args...);
            std::cout << "test bind args..." << std::endl;
            func_();
        }
    }

    template<typename... T>
    void Test(T&... args)
    {
        std::cout << "call Test " << std::endl;
    }

    std::function<void()> func_;

};

int  TestMyBind()
{
    int a = 6;
    double b = 3.9;
    std::string c{ "test bind" };

    MyBind myBind;
    myBind.Call(a, b, c);

    return 0;
}

都是需要认真看才能搞清楚,所以看这些例子,还是要仔细静下心来,弄明白了,就真的掌握了。不要看看会,就觉得会了,自己写一个试试,可能会有意外的惊喜。
这三个例子,都是比较好的应用场景,在这些简单的例子的入门引导下,就可以不断的在实际的应用中完善修改,最终形成一个符合自己工程应用的模块,最合适的才是最好的。

三、总结

学以致用,这句话说起来容易,可做起来真难啊。如果不能深刻的理解一门技术,就无法自如的应用他,而只能照猫画虎的外向型模仿。不过,这种外向型模仿却又是深刻理解的前提和基础。学习本身就是一个由表及里,由外到内的过程。越是学习应用的多,对一门技术的理解就会越发的深刻。而反过来,应用这门技术就会越发的灵活自如。
计算机技术就是如此,多读书,勤实践,不断的从理论到实践并不断的反馈自己对理论的理解,如此往复,其期可待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值