C++标准库学习之 Wrapper 外覆器

47 阅读5分钟

std::reference_wrapper 引用外覆器

在C++ 中有一个 ReferenceWrapper 引用外覆器 他的包是 < functional >, 讲到这个就不得不讲一下 C++ 方法之间的数据传递关系 先看下面例子

#include <iostream>

using namespace std;
class Tsm{
public:
    int a=10;
    Tsm(){
        cout<<"无参数构方法"<< endl;
    }
    Tsm(Tsm& tsm){
        cout<<"拷贝构造函数"<< endl;
    }
    ~Tsm(){
        cout<<"析构函数"<< endl;
    }
};

void func1(Tsm tsm){
    tsm.a=100;
}

int main(){
    Tsm tsm;
    cout<<"--------开始调用方法----------"<<endl;
    func1(tsm);
    cout<<"--------结束调用方法---------"<<endl;
    cout<<tsm.a<<endl;
    return 0;
}

在这个例子中,创建了一个Tsm 的class , 在他的无参构造方法,析构函数,拷贝构造函数中打印了一下各自的名称,在main 方法中先创建了一个 Tsm ,使用 这个创建的 Tsm 调用了 func1 这个方法,结果打印如下

结果:

D:\CWorkSpace\tsmTest\cmake-build-debug\tsmTest.exe
无参数构方法
--------开始调用方法----------
拷贝构造函数
析构函数
--------结束调用方法---------
10
析构函数

Process finished with exit code 0

发现在main 方法中,我们虽然传递进去的是 我们创建的 tsm , 但是编译器会自动复制一个Tsm 对象,传入这个方法中,这就导致我们如果在 func1 中即使对这个对象做了修改也不会影响到 main 中的 对象,

将例子修改一下

void func1(Tsm& tsm){ tsm.a=100; }

这里我们将引入传入 func1 这个方法中,看看打印情况

D:\CWorkSpace\tsmTest\cmake-build-debug\tsmTest.exe
无参数构方法
--------开始调用方法----------
--------结束调用方法---------
100
析构函数

Process finished with exit code 0

发现在 func1 方法中如果使用引用的话,在方法传递的过程中就没有再创建新的对象了,同时修改了他的属性也会影响到main 里面了,但是使用了引用后又出现了另一种情况,那就是在 C++ 中 vector 等容器是无法接受引用的,那么该如何解决呢,下面先看这个问题是如何产生的

#include <iostream>
#include <vector>
#include <functional>


using namespace std;
class Tsm{
public:
    int a=10;
    Tsm(){
        cout<<"无参数构方法"<< endl;
    }
    Tsm(const Tsm& tsm){
        cout<<"拷贝构造函数"<< endl;
    }
    ~Tsm(){
        cout<<"析构函数"<< endl;
    }
};

int main(){
    Tsm tsm;
    vector<Tsm> v;
    cout<<"--------开始调用方法----------"<<endl;
    v.push_back(tsm);
    cout<<"--------结束调用方法---------"<<endl;
    return 0;
}

还是一个非常简单的例子,那就是使用 vector 向量来缓存我们的 Tsm , 在调用了 v.push_back 方法时,我们来看一下Tsm 构造方法打印的情况

D:\CWorkSpace\tsmTest\cmake-build-debug\tsmTest.exe
无参数构方法
--------开始调用方法----------
拷贝构造函数
--------结束调用方法---------
析构函数
析构函数

Process finished with exit code 0

发现在向 Vector 中添加数据的时候,还是导致我们的数据是被拷贝了一份,这就代表着我们存进去的Tsm 与 vector 中的tsm 是两个对象,这对于数据的处理和使用是非常不利的,那我们也是引用来解决一下

image.png

发现在给vector 中添加引用是不可以的,那么我们改如何解决这个问题呢

引用外覆器就帮我们解决了这个问题, 先来看看 他是如何解决的,以及他是如何使用的

#include <iostream>
#include <vector>
#include <functional>


using namespace std;
class Tsm{
public:
    int a=10;
    Tsm(){
        cout<<"无参数构方法"<< endl;
    }
    Tsm(const Tsm& tsm){
        cout<<"拷贝构造函数"<< endl;
    }
    ~Tsm(){
        cout<<"析构函数"<< endl;
    }
};


vector<std::reference_wrapper<Tsm>> vec;

void func1(Tsm& tsm){
    tsm.a=100;
    vec.push_back(std::ref(tsm));
}

int main(){
    Tsm tsm;
    vector<Tsm> v;
    cout<<"--------开始调用方法----------"<<endl;
    func1(tsm);
    cout<<"--------结束调用方法---------"<<endl;

    std::reference_wrapper<Tsm> t=vec.back();
    cout << (t.get().a)  <<endl;

    return 0;
}

结果:

D:\CWorkSpace\tsmTest\cmake-build-debug\tsmTest.exe
无参数构方法
--------开始调用方法----------
--------结束调用方法---------
100
析构函数

Process finished with exit code 0

这个结果告诉我们,在方法调用期间,为了节省内存,以及对原有数据类型做修改,我们要尽量使用引用来传递对象,在 使用容器保存对象时,reference_wrapper 是一个非常重要的选择,

再来看看 std::reference_wrapper的源码

 template<typename _Tp>
   class reference_wrapper
   : public _Reference_wrapper_base<typename remove_cv<_Tp>::type>
   {
     _Tp* _M_data;

   public:
     typedef _Tp type;

     reference_wrapper(_Tp& __indata) noexcept
     : _M_data(std::__addressof(__indata))
     { }

     reference_wrapper(_Tp&&) = delete;

     reference_wrapper(const reference_wrapper&) = default;

     reference_wrapper&
     operator=(const reference_wrapper&) = default;

     operator _Tp&() const noexcept
     { return this->get(); }

     _Tp&
     get() const noexcept
     { return *_M_data; }

     template<typename... _Args>
typename result_of<_Tp&(_Args&&...)>::type
operator()(_Args&&... __args) const
{
  return std::__invoke(get(), std::forward<_Args>(__args)...);
}
   };

他的源码也比较简单,就是一个包装类, 保存了对象的地址,我相信了解了这些知识后再去看他的使用会让你有种拨云见雾的感觉

std::function<> 方法外覆器

std::function<> 这个对象可让我们将一个可调用对象 比如方法 等当做一个高级对象,帮助我们来执行某些操作,

下面写一个我在 Hippy 引擎中看到的案例,这里只是简单用法

#include <iostream>
#include <functional>
#include <queue>

using namespace std;

int main() {
    queue<std::function<void()>> tasks;
    int a=5;
    int b=10;
    cout<< "开始添加方法入队" <<endl;
    tasks.push([a, b] {
        cout << (a+b)<<endl;
    });

    tasks.push([a] {
        cout << (a)<<endl;
    });

    cout<< "从队列中取出方法" <<endl;
    auto task=tasks.front();
    cout<< "执行方法" <<endl;
    task();
    tasks.pop();

    auto task2=tasks.front();
    cout<< "执行方法" <<endl;
    task2();
    return 0;
}

结果:

D:\CWorkSpace\tsmTest\cmake-build-debug\tsmTest.exe
开始添加方法入队
从队列中取出方法
执行方法
15
执行方法
5

Process finished with exit code 0

在这个例子中,我们显示创建了一个queue 队列, 并向队列中添加了一个 std::function<void()> 参数个数不固定的方法, 这样我们就可以在需要的时候,不断的从 queue 中取出这个高级变量来执行我们的操作,