C++11 std::function, std::bind, std::ref, std::cref

C++11 std::function, std::bind, std::ref, std::cref

轉自:http://www.jellythink.com/

std::function

看看這段代碼

先來看看下面這兩行代碼:

std::function<void(EventKeyboard::KeyCode, Event*)> onKeyPressed;
std::function<void(EventKeyboard::KeyCode, Event*)> onKeyReleased;

這兩行代碼是從Cocos2d-x中摘出來的,重點是這兩行代碼的定義啊。std::function這是什么東西?如果你對上述兩行代碼表示毫無壓力,那就不妨再看看本文,就當溫故而知新吧。

std::function介紹

類模版std::function是一種通用、多態的函數封裝。std::function的實例可以對任何可以調用的目標實體進行存儲、復制、和調用操作,這些目標實體包括普通函數、Lambda表達式、函數指針、以及其它函數對象等。std::function對象是對C++中現有的可調用實體的一種類型安全的包裹(我們知道像函數指針這類可調用實體,是類型不安全的)。

通常std::function是一個函數對象類,它包裝其它任意的函數對象,被包裝的函數對象具有類型為T1, …,TN的N個參數,并且返回一個可轉換到R類型的值。std::function使用 模板轉換構造函數接收被包裝的函數對象;特別是,閉包類型可以隱式地轉換為std::function

最簡單的理解就是:

通過std::function對C++中各種可調用實體(普通函數、Lambda表達式、函數指針、以及其它函數對象等)的封裝,形成一個新的可調用的std::function對象;讓我們不再糾結那么多的可調用實體。一切變的簡單粗暴。

怎么使用std::function

使用std::function的感覺就是“萬眾歸一”,下面就通過實際的代碼例子,看看究竟怎么使用std::function。會使用了才是王道。

#include <functional>
#include <iostream>
using namespace std;std::function< int(int)> Functional;// 普通函數
int TestFunc(int a)
{return a;
}// Lambda表達式
auto lambda = [](int a)->int{ return a; };// 仿函數(functor)
class Functor
{
public:int operator()(int a){return a;}
};// 1.類成員函數
// 2.類靜態函數
class TestClass
{
public:int ClassMember(int a) { return a; }static int StaticMember(int a) { return a; }
};int main()
{// 普通函數Functional = TestFunc;int result = Functional(10);cout << "普通函數:"<< result << endl;// Lambda表達式Functional = lambda;result = Functional(20);cout << "Lambda表達式:"<< result << endl;// 仿函數Functor testFunctor;Functional = testFunctor;result = Functional(30);cout << "仿函數:"<< result << endl;// 類成員函數TestClass testObj;Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);result = Functional(40);cout << "類成員函數:"<< result << endl;// 類靜態函數Functional = TestClass::StaticMember;result = Functional(50);cout << "類靜態函數:"<< result << endl;return 0;
}

對于各個可調用實體轉換成std::function類型的對象,上面的代碼都有,運行一下代碼,閱讀一下上面那段簡單的代碼。總結了簡單的用法以后,來看看一些需要注意的事項:

  • 關于可調用實體轉換為

    std::function
    

    對象需要遵守以下兩條原則:

    • 轉換后的std::function對象的參數能轉換為可調用實體的參數;
    • 可調用實體的返回值能轉換為std::function對象的返回值。
  • std::function對象最大的用處就是在實現函數回調,使用者需要注意,它不能被用來檢查相等或者不相等,但是可以與NULL或者nullptr進行比較。

為什么要用std::function

好用并實用的東西才會加入標準的。因為好用,實用,我們才在項目中使用它。std::function實現了一套類型消除機制,可以統一處理不同的函數對象類型。以前我們使用函數指針來完成這些;現在我們可以使用更安全的std::function來完成這些任務。

還有為什么?我也不知道還有為什么?等以后發現了更好的實際應用實例再回來說為什么吧。

std::bind

看看這段代碼

這幾天學習Cocos2d-x,看到了以下的一段代碼:

// new callbacks based on C++11
#define CC_CALLBACK_0(__selector__,__target__, ...) std::bind(&__selector__,__target__, ##__VA_ARGS__)#define CC_CALLBACK_1(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, ##__VA_ARGS__)#define CC_CALLBACK_2(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, ##__VA_ARGS__)#define CC_CALLBACK_3(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__)

都是定義的一些宏,如果你看了上面的這段代碼,覺的很簡單,那么這篇文章你完全可以pass了;如果你對上面定義的這些宏,完全不知道是什么意思,那么,這篇文章就屬于你,完全屬于你的菜。

通過這篇文章,我將帶你進入C++11中std::bind的世界,讓我們起航吧。

先來看看std::bind1st和std::bind2nd

bind是這樣一種機制,它可以預先把指定可調用實體的某些參數綁定到已有的變量,產生一個新的可調 用實體,這種機制在回調函數的使用過程中也頗為有用。C++98中,有兩個函數bind1st和bind2nd,它們分別可以用來綁定functor的第 一個和第二個參數,它們都是只可以綁定一個參數。各種限制,使得bind1st和bind2nd的可用性大大降低。

如果通過上面的內容,你還沒有明白std::bind1st和std::bind2nd到底是何方圣神到底是什么東西,那就看這段代碼示例:

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;int main()
{vector<int> coll;for (int i = 1; i <= 10; ++i){coll.push_back(i);}// 查找元素值大于10的元素的個數// 也就是使得10 < elem成立的元素個數 int res = count_if(coll.begin(), coll.end(), bind1st(less<int>(), 10));cout << res << endl;// 查找元素值小于10的元素的個數// 也就是使得elem < 10成立的元素個數 res = count_if(coll.begin(), coll.end(), bind2nd(less<int>(), 10));cout << res << endl;return 0;
}

通過上面的代碼明白了std::bind1st和std::bind2nd了么?還沒有明白?好吧,我接著往細了講。

對于上面的代碼,less<int>()其實是一個仿函數,如果沒有std::bind1st和std::bind2nd,那么我們可以這樣使用less<int>(),代碼如下:

less<int> functor = less<int>();
bool bRet = functor(10, 20); // 返回true

看到了么?less<int>()這個仿函數對象是需要兩個參數的,比如10<20進行比較,那么10叫做left參數,20叫做right參數。

  • 當使用std::bind1st的時候,就表示綁定了left參數,也就是left參數不變了,而right參數就是對應容器中的element;
  • 當使用std::bind2nd的時候,就表示綁定了right參數,也就是right參數不變了,而left參數就是對應容器中的element。

這下應該講明白了。

再來看看std::bind

C++11中提供了std::bind。bind()函數的意義就像它的函數名一樣,是用來綁定函數調用的某些參數的。

bind的思想實際上是一種延遲計算的思想,將可調用對象保存起來,然后在需要的時候再調用。而且這種綁定是非常靈活的,不論是普通函數、函數對象、還是成員函數都可以綁定,而且其參數可以支持占位符,比如你可以這樣綁定一個二元函數auto f = bind(&func, _1, _2);,調用的時候通過f(1,2)實現調用。

簡單的認為就是std::bind就是std::bind1ststd::bind2nd的加強版。

怎么使用std::bind

一個知識點厲不厲害,歸根到底還是要經過實踐的考驗,下面就來看看std::bind到底怎么用。

先看看《C++11中的std::function》中那段代碼,std::function可以綁定全局函數,靜態函數,但是綁定類的成員函數時,必須要借助std::bind的幫忙。但是話又說回來,不借助std::bind也是可以完成的,只需要傳一個*this變量進去就好了,比如:

 
#include <iostream>
#include <functional>
using namespace std;class View
{
public:void onClick(int x, int y){cout << "X : " << x << ", Y : " << y << endl;}
};// 定義function類型, 三個參數
function<void(View, int, int)> clickCallback;int main(int argc, const char * argv[])
{View button;// 指向成員函數clickCallback = &View::onClick;// 進行調用clickCallback(button, 10, 123);return 0;
}

再來一段示例談談怎么使用std::bind代碼:

#include <iostream>
#include <functional>
using namespace std;int TestFunc(int a, char c, float f)
{cout << a << endl;cout << c << endl;cout << f << endl;return a;
}int main()
{auto bindFunc1 = bind(TestFunc, std::placeholders::_1, 'A', 100.1);bindFunc1(10);cout << "=================================\n";auto bindFunc2 = bind(TestFunc, std::placeholders::_2, std::placeholders::_1, 100.1);bindFunc2('B', 10);cout << "=================================\n";auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);bindFunc3(100.1, 30, 'C');return 0;
}

上面這段代碼主要說的是bind中std::placeholders的使用。 std::placeholders是一個占位符。當使用bind生成一個新的可調用對象時,std::placeholders表示新的可調用對象的第 幾個參數和原函數的第幾個參數進行匹配,這么說有點繞。比如:

auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);bindFunc3(100.1, 30, 'C');

可以看到,在bind的時候,第一個位置是TestFunc,除了這個,參數的第一個位置為占位符std::placeholders::_2,這就表示,調用bindFunc3的時候,它的第二個參數和TestFunc的第一個參數匹配,以此類推。

以下是使用std::bind的一些需要注意的地方:

  • bind預先綁定的參數需要傳具體的變量或值進去,對于預先綁定的參數,是pass-by-value的;
  • 對于不事先綁定的參數,需要傳std::placeholders進去,從_1開始,依次遞增。placeholder是pass-by-reference的;
  • bind的返回值是可調用實體,可以直接賦給std::function對象;
  • 對于綁定的指針、引用類型的參數,使用者需要保證在可調用實體調用之前,這些參數是可用的;
  • 類的this可以通過對象或者指針來綁定。

為什么要使用std::bind

當我們厭倦了使用std::bind1ststd::bind2nd的時候,現在有了std::bind,你完全可以放棄使用std::bind1ststd::bind2nd了。std::bind綁定的參數的個數不受限制,綁定的具體哪些參數也不受限制,由用戶指定,這個bind才是真正意義上的綁定。

在Cocos2d-x中,我們可以看到,使用std::bind生成一個可調用對象,這個對象可以直接賦值給std::function對象;在類中有一個std::function的變量,這個std::functionstd::bind來賦值,而std::bind綁定的可調用對象可以是Lambda表達式或者類成員函數等可調用對象,這個是Cocos2d-x中的一般用法。

以后遇到了“奇葩”用法再繼續總結了,一次也總結不完的。

又是一篇總結怎么使用的文章,如果你覺的看的不過癮,覺的我的文章寫的不痛不癢的,還想看點更深的東西,比如std::bind是如何實現的啊?好吧,這篇文章確實沒有說這些深層次的東西,推薦這篇文章《bind原理圖釋》,希望這篇文章能滿足你哦。

std::ref, std::cref

C++本身有引用(&),為什么C++11又引入了std::ref(或者std::cref)?

主要是考慮函數式編程(**如std::bind、std::thread)**在使用時,是對參數直接拷貝,而不是引用。std::bind()是一個函數模板,它的原理是根據已有的模板,生成一個函數,但是由于bind()不知道生成的函數執行的時候,傳遞進來的參數是否還有效。所以它選擇參數值傳遞而不是引用傳遞。如果想引用傳遞,std::ref和std::cref就派上用場了。如下例子:

#include <functional>
#include <iostream>void f(int& n1, int& n2, const int& n3)
{std::cout << "In function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';++n1; // increments the copy of n1 stored in the function object++n2; // increments the main()'s n2// ++n3; // compile error
}int main()
{int n1 = 1, n2 = 2, n3 = 3;std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3));n1 = 10;n2 = 11;n3 = 12;std::cout << "Before function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';bound_f();std::cout << "After function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
}

輸出:

Before function: 10 11 12
In function: 1 11 12
After function: 10 12 12

上述代碼在執行std::bind后,在函數f()中n1的值仍然是1,n2和n3改成了修改的值。說明std::bind使用的是參數的拷貝而不是引用。具體為什么std::bind不使用引用,可能確實有一些需求,使得C++11的設計者認為默認應該采用拷貝,如果使用者有需求,加上std::ref即可。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532554.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532554.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532554.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java安全(一) : java類 | 反射

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.java基礎 Java平臺共分為三個主要版本Java SE&#xff08;Java Platform, Standard Edition&#xff0c;Java平臺標準版&#xff09;、Java EE&#xff0…

LeetCode-287 尋找重復數 二分法

LeetCode-287 尋找重復數 二分法 287. 尋找重復數 給定一個包含 n 1 個整數的數組 nums &#xff0c;其數字都在 1 到 n 之間&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一個重復的整數。 假設 nums 只有 一個重復的整數 &#xff0c;找出 這個重復的數 。…

對某公司一次弱口令到存儲型xss挖掘

轉自我的奇安信攻防社區文章:https://forum.butian.net/share/885 免責聲明: 滲透過程為授權測試,所有漏洞均以提交相關平臺,博客目的只為分享挖掘思路和知識傳播** 涉及知識: xss注入及xss注入繞過 挖掘過程: 某次針對某目標信息搜集無意發現某工程公司的項目招標平臺 …

C++11新特性選講 語言部分 侯捷

C11新特性選講 語言部分 侯捷 本課程分為兩個部分&#xff1a;語言的部分和標準庫的部分。只談新特性&#xff0c;并且是選講。 本文為語言部分筆記。 語言 Variadic Templatesmove semanticsautoRange-based for loopInitializer listLambdas… 標準庫 type_traitsunodered…

java安全(二):JDBC|sql注入|預編譯

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1 JDBC基礎 JDBC(Java Database Connectivity)是Java提供對數據庫進行連接、操作的標準API。Java自身并不會去實現對數據庫的連接、查詢、更新等操作而是通…

java安全(三)RMI

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.RMI 是什么 RMI(Remote Method Invocation)即Java遠程方法調用&#xff0c;RMI用于構建分布式應用程序&#xff0c;RMI實現了Java程序之間跨JVM的遠程通信…

LeetCode-726 原子的數量 遞歸

LeetCode-726 原子的數量 遞歸 題目鏈接&#xff1a;LeetCode-726 原子的數量 給你一個字符串化學式 formula &#xff0c;返回 每種原子的數量 。 原子總是以一個大寫字母開始&#xff0c;接著跟隨 0 個或任意個小寫字母&#xff0c;表示原子的名字。 如果數量大于 1&#xf…

java安全(四) JNDI

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.JNDI JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目錄接口。通過調用JNDI的API應用程序可以定位資源和其他程序對象。JNDI是Java…

二叉樹的層序遍歷和前中后序遍歷代碼 迭代/遞歸

前中后序遍歷&#xff08;DFS&#xff09; 首先我們要明確前中后序遍歷的順序&#xff1a; 前序&#xff1a;中左右中序&#xff1a;左中右后序&#xff1a;左右中 前中后序遍歷的遞歸代碼和迭代代碼分別有各自的框架&#xff0c;然后根據遍歷順序調整記錄元素的位置即可。 …

java安全(五)java反序列化

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1. 序列化 在調用RMI時,發現接收發送數據都是反序列化數據. 例如JSON和XML等語言,在網絡上傳遞信息,都會用到一些格式化數據,大多數處理方法中&#xff0c…

git merge和rebase的區別與選擇

git merge和rebase的區別與選擇 轉自&#xff1a;https://github.com/geeeeeeeeek/git-recipes/wiki/5.1-%E4%BB%A3%E7%A0%81%E5%90%88%E5%B9%B6%EF%BC%9AMerge%E3%80%81Rebase-%E7%9A%84%E9%80%89%E6%8B%A9#merge BY 童仲毅&#xff08;geeeeeeeeekgithub&#xff09; 這是一篇…

java安全(六)java反序列化2,ysoserial調試

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; ysoserial 下載地址&#xff1a;https://github.com/angelwhu/ysoserial ysoserial可以讓?戶根據??選擇的利?鏈&#xff0c;?成反序列化利?數據&…

C++面試常見問題一

C面試常見問題一 轉自&#xff1a;https://oldpan.me/archives/c-interview-answer-1 原作者&#xff1a;[oldpan][https://oldpan.me/] 前言 這里收集市面上所有的關于算法和開發崗最容易遇到的關于C方面的問題&#xff0c;問題信息來自互聯網以及牛客網的C面試題目匯總。答題…

java安全(七) 反序列化3 CC利用鏈 TransformedMap版

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 目錄圖解代碼demo涉及的接口與類&#xff1a;TransformedMapTransformerConstantTransformerInvokerTransformerChainedTransformerdome理解總結&#xff1a…

C++編譯時多態和運行時多態

C編譯時多態和運行時多態 作者&#xff1a;melonstreet 出處&#xff1a;https://www.cnblogs.com/QG-whz/p/5132745.html 本文版權歸作者和博客園共有&#xff0c;歡迎轉載&#xff0c;但未經作者同意必須保留此段聲明&#xff0c;且在文章頁面明顯位置給出原文連接&#xff0…

java安全(八)TransformedMap構造POC

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 上一篇構造了一個了commons-collections的demo 【傳送門】 package test.org.vulhub.Ser;import org.apache.commons.collections.Transformer; import org…

Pytorch Tutorial 使用torch.autograd進行自動微分

Pytorch Tutorial 使用torch.autograd進行自動微分 本文翻譯自 PyTorch 官網教程。 原文&#xff1a;https://pytorch.org/tutorials/beginner/basics/autogradqs_tutorial.html#optional-reading-tensor-gradients-and-jacobian-products 在訓練神經網絡時&#xff0c;最常使用…

TVM:編譯深度學習模型快速上手教程

TVM&#xff1a;編譯深度學習模型快速上手教程 本文將展示如何使用 Relay python 前端構建一個神經網絡&#xff0c;并使用 TVM 為 Nvidia GPU 生成一個運行時庫。 注意我們需要再構建 TVM 時啟用了 cuda 和 llvm。 TVM支持的硬件后端總覽 在本教程中&#xff0c;我們使用 cu…

TVM:設計與架構

TVM&#xff1a;設計與架構 本文檔適用于想要了解 TVM 架構和/或積極開發項目的開發人員。頁面組織如下&#xff1a; 示例編譯流程概述了 TVM 將模型的高層描述轉換為可部署模塊所采取的步驟。要開始使用&#xff0c;請先閱讀本節。 邏輯架構組件部分描述了邏輯組件。后面的部…

遞歸+回溯

遞歸-回溯 本文參考自代碼隨想錄視頻&#xff1a; https://www.bilibili.com/video/BV1cy4y167mM https://www.bilibili.com/video/BV1ti4y1L7cv 遞歸回溯理論基礎 只要有遞歸&#xff0c;就會有回溯&#xff0c;遞歸函數的下面的部分通常就是回溯的邏輯。 回溯是純暴力的搜索…