专业编程基础技术教程

网站首页 > 基础教程 正文

C++ std:decay、std:bind、std:packaged_task 在模版编程的实践

ccvgpt 2024-11-11 11:24:19 基础教程 6 ℃

一、std::decay

参考:实现thread库细节:使用std::decay保存函数指针 | BewareMyPower的博客

C++ Primer中提到,在模板类型推导中,一般的类型转换是禁止的,否则无法准确推断是哪种类型。但是两种类型转换是允许的:

C++ std:decay、std:bind、std:packaged_task 在模版编程的实践

  1. 将非const的引用或指针传递给const的引用或指针形参,比如
template <typename T> void f(const T&);  // 函数模板声明

int i = 0;
int& ri = i;
f(ri);  // ri被转换成const int&类型,因此T被推断为int
  1. 若形参不是引用类型,数组实参会被转换成指针形参,函数实参会被转换成函数指针形参,比如
template <typename T> void f(T param);

void func();
f(func);  // T被推断为void(*)()

int a[10];
f(a);     // T被推断为int*

decay完成的功能即将数组和函数转换成退化后的类型,对于其他类型,则是移除引用以及constvolatile描述符,分别对应上述的2和1。
注意,在模板类型推断中,实参的引用类型都会被忽略,就像上述1中的
const被忽略一样。
比如传入
int&到形参T t中,推断方式是:先忽略&,然后匹配intT,因此T永远不会被推断为引用类型,除非形参是右值引用T&& t,根据引用折叠规则(&& &&折叠为&&&& && &&& &被折叠为&),才有可能推断T为引用,这也是C++11实现完美转发的基础。
从类型
T到退化类型typename std::decay<T>::type的映射示例如下

int () => int (*)()
int (&)() => int (*)()
int [10] => int*
int (&) [10] => int*
int const => int
int const& => int
int const* => int const*
int* const => int*


二、std::packaged_task

参考:C++11 并发指南四(<future> 详解二 std::packaged_task 介绍) - Haippy - 博客园

std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task 与 std::function 类似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象(该对象通常在另外一个线程中获取 std::packaged_task 任务的执行结果)。

std::packaged_task 对象内部包含了两个最基本元素,一、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,二、共享状态(shared state),用于保存任务的返回值,可以通过 std::future 对象来达到异步访问共享状态的效果。

可以通过 std::packged_task::get_future 来获取与共享状态相关联的 std::future 对象。在调用该函数之后,两个对象共享相同的共享状态,具体解释如下:

  • std::packaged_task 对象是异步 Provider,它在某一时刻通过调用被包装的任务来设置共享状态的值。
  • std::future 对象是一个异步返回对象,通过它可以获得共享状态的值,当然在必要的时候需要等待共享状态标志变为 ready.

std::packaged_task 的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止

#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
    for (int i=from; i!=to; --i) {
        std::cout << i << '\n';
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Finished!\n";
    return from - to;
}

int main ()
{
    std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
    std::future<int> ret = task.get_future(); // 获得与 packaged_task 共享状态相关联的 future 对象.

    std::thread th(std::move(task), 10, 0);   //创建一个新线程完成计数任务.

    int value = ret.get();                    // 等待任务完成并获取结果.

    std::cout << "The countdown lasted for " << value << " seconds.\n";

    th.join();
    return 0;
}

三、std::bind

std::bind的头文件是 <functional>,它是一个函数适配器,接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。函数原型

std::bind函数有两种函数原型,定义如下:

template< class F, class... Args >

/*unspecified*/ bind( F&& f, Args&&... args );

template< class R, class F, class... Args >

/*unspecified*/ bind( F&& f, Args&&... args );

std::bind返回一个基于f的函数对象,其参数被绑定到args上。

f的参数要么被绑定到值,要么被绑定到placeholders(占位符,如_1, _2, ..., _n)。

std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:

将可调用对象和其参数绑定成一个防函数;

只绑定部分参数,减少可调用对象传入的参数。

1 std::bind绑定普通函数

double callableFunc (double x, double y) {return x/y;}

auto NewCallable = std::bind (callableFunc, std::placeholders::_1,2);

std::cout << NewCallable (10) << '\n';

bind的第一个参数是函数名,普通函数做实参时,会隐式转换成函数指针。因此std::bind(callableFunc,_1,2)等价于std::bind (&callableFunc,_1,2);

_1表示占位符,位于<functional>中,std::placeholders::_1;

第一个参数被占位符占用,表示这个参数以调用时传入的参数为准,在这里调用NewCallable时,给它传入了10,其实就想到于调用callableFunc(10,2);

2 std::bind绑定一个成员函数

class Base

{

public:

void display_sum(int a1, int a2)

{

std::cout << a1 + a2 << '\n';

}

int m_data = 30;

};

int main()

{

Base base;

auto newiFunc = std::bind(&Base::display_sum, &base, 100, std::placeholders::_1);

f(20); // should out put 120.

}

bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址。

必须显式地指定&Base::diplay_sum,因为编译器不会将对象的成员函数隐式转换成函数指针,所以必须在Base::display_sum前添加&;

使用对象成员函数的指针时,必须要知道该指针属于哪个对象,因此第二个参数为对象的地址 &base;

3 绑定一个引用参数

默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但是,与lambda类似,有时对有些绑定的参数希望以引用的方式传递,或是要绑定参数的类型无法拷贝。

#include <iostream>

#include <functional>

#include <vector>

#include <algorithm>

#include <sstream>

using namespace std::placeholders;

using namespace std;

ostream & printInfo(ostream &os, const string& s, char c)

{

os << s << c;

return os;

}

int main()

{

vector<string> words{"welcome", "to", "C++11"};

ostringstream os;

char c = ' ';

for_each(words.begin(), words.end(),

[&os, c](const string & s){os << s << c;} );

cout << os.str() << endl;

ostringstream os1;

// ostream不能拷贝,若希望传递给bind一个对象,

// 而不拷贝它,就必须使用标准库提供的ref函数

for_each(words.begin(), words.end(),

bind(printInfo, ref(os1), _1, c));

cout << os1.str() << endl;

最近发表
标签列表