std::ref 用法與範例

本篇 ShengYu 介紹 C++ std::ref 用法與範例,

C++ 要使用 std::ref 的話,需要引入的標頭檔<functional>

以下內容將分為這幾部份,

  • 簡單的 std::ref 用法
  • 已經有 & ,那什麼時候會用到 std::ref?
  • std::ref 與 std::cref
  • std::ref() 與 & 的轉換關係

簡單的 std::ref 用法

簡單的 std::ref 用法如下,感覺很像 & reference 參考,對 std::ref() 產生出來的 n2 做修改可以影響原本的 n1,

std-ref.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// g++ std-ref.cpp -o a.out -std=c++11
#include <iostream>
#include <functional>

int main () {
int n1 = 5;

auto n2 = std::ref(n1);

n2++;
std::cout << "n1=" << n1 << ",n2=" << n2 << "\n";

n1++;
std::cout << "n1=" << n1 << ",n2=" << n2 << "\n";

return 0;
}

輸出結果如下,

1
2
n1=6,n2=6
n1=7,n2=7

已經有 & ,那什麼時候會用到 std::ref?

你可能會疑惑 C++ 已經有 & 參考了,那 C++11 新增的 std::ref 是幹麻用的呢?
那我們就來看看什麼時候用 & 參考,什麼時候用 std::ref,
某天我有個 myfunc 是用來傳參考並在 myfunc 裡面對 n 作一些計算,之後在 main 可以取得 n 的計算結果,這個傳參考的方式相信大家都很熟了,傳參考不熟的可以看這篇

std-ref2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// g++ std-ref2.cpp -o a.out
#include <iostream>

void myfunc(int& n) {
std::cout << "myfunc n=" << n << '\n';
n++;
}

int main() {
int n = 0;
myfunc(n);
std::cout << "main n=" << n << '\n';
return 0;
}

輸出如下,

1
2
myfunc n=0
main n=1

後來我有個需求要另外開個執行緒來做 myfunc 這件事,在之前我們已經有介紹過怎麼建立執行緒,所以我們馬上開始改寫成下面這樣,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <thread>

void myfunc(int& n) {
std::cout << "myfunc n=" << n << '\n';
n++;
}

int main() {
int n = 0;
std::thread t1(myfunc, n);
t1.join();
std::cout << "main n=" << n << '\n';
return 0;
}

疑?!結果居然出現編譯錯誤,

1
2
3
4
5
6
7
8
9
/usr/include/c++/5/functional: In instantiation of ‘struct std::_Bind_simple<void (*(int))(int&)>’:
/usr/include/c++/5/thread:137:59: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(int&); _Args = {int&}]’
std-ref4.cpp:12:29: required from here
/usr/include/c++/5/functional:1505:61: error: no type named ‘type’ in ‘class std::result_of<void (*(int))(int&)>’
typedef typename result_of<_Callable(_Args...)>::type result_type;
^
/usr/include/c++/5/functional:1526:9: error: no type named ‘type’ in ‘class std::result_of<void (*(int))(int&)>’
_M_invoke(_Index_tuple<_Indices...>)
^

如果改成 myfunc(int n) 就可以編譯成功,但這樣是傳值不是我想要的,
那麼開執行緒執行 myfunc(int& n) 參數傳參考要如何寫呢?
透過查詢文件與了解後,發現原來像 std::thread() 或 std::bind() 這種函式裡的參數傳遞方式為傳值(call by value),如果要傳參考的話,就要使用 std::ref 輔助類別來達成,也就是我現在這種開執行緒執行 myfunc 函式時參數是傳參考時,需要在 std::thread 建構時帶入的參數使用 std::ref() 來達到傳參考的目的啦!

原來如此,那馬上來修改,修改後的程式碼變這樣,

std-ref3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ std-ref3.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <thread>

void myfunc(int& n) {
std::cout << "myfunc n=" << n << '\n';
n++;
}

int main() {
int n = 0;
std::thread t1(myfunc, std::ref(n));
t1.join();
std::cout << "main n=" << n << '\n';
return 0;
}

馬上在 std::thread 開執行緒時傳遞參數改成 std::ref(n) 就可以編譯成功了,輸出結果如下,由此可見 std::thread() 預設使用的是參數傳遞方式是傳值 call by value 而不是傳參考 call by reference,關於 call by value 與 call by reference 這兩個的參數傳遞差異可以看這篇

1
2
myfunc n=0
main n=1

std::ref 與 std::cref

透過上述的漸進式說明,我們了解了 std::ref 主要是考慮函數式程式設計 functional programming在使用時(例如 std::bind)是對參數傳值,而不是傳參考。

我們知道了 std::ref 用於包裝傳參考之外,那麼 const 傳參考呢?答案就是 std::cref,我們來看看下面 std::bind 例子就可以很清楚了解這兩者之間的差異了,std::bind() 是一個函式樣板 function template,它的原理是根據已有的樣板,生成一個函式,但是由於 std::bind() 不知道生成的函式執行的時候,傳遞進來的參數是否還有效。所以它選擇傳值 call by value 而不是傳參考 call by reference。如果要傳參考就要使用 std::ref 或 std::cref。

std-ref4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// g++ std-ref4.cpp -o a.out -std=c++11
#include <iostream>
#include <functional>

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 = 0, n2 = 0, n3 = 0;
std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3));
n1 = 1;
n2 = 1;
n3 = 1;
std::cout << "Before function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
bound_f();
std::cout << "After function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
return 0;
}

結果輸出如下,

1
2
3
Before function: 1 1 1
In function: 0 1 1
After function: 1 2 1

在 std::bind 後面的參數中,n1 是傳值,所以 f 函式內部的 n1 修改不會影響到 main 的 n1,因為傳值是在 f 函式裡複製一份(副本),
n2 是傳參考,所以 f 函式內部的 n2 修改會影響到 main 的 n2,
n3 是 const 傳參考,所以在 f 函式內部裡不能對 n3 修改,如果嘗試修改 n3 的話就會編譯錯誤。

std::ref() 與 & 的轉換關係

std::ref() 和 std::cref() 是輔助類別會回傳 std::reference_wrapper 類別樣板 class template,用於傳遞物件的參考給 std::bind 或 std::thread 建構子時,
所以它到底跟 & 有什麼關係呢?我們來看看下面這段程式了解它們之間的型別關係,

std-ref5.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// g++ std-ref5.cpp -o a.out -std=c++11
#include <iostream>
#include <type_traits>
#include <functional>

int main() {
int n1 = 5;
auto n2 = std::ref(n1);
std::reference_wrapper<int> n3 = std::ref(n1);

std::cout << std::boolalpha <<
std::is_same<int&, decltype(n2)>::value << "\n";

std::cout << std::boolalpha <<
std::is_same<int&, decltype(n2.get())>::value << "\n";

n1 = 10;
std::cout << "n1=" << n1 << ",n2=" << n2 << ",n3=" << n3 << "\n";

return 0;
}

輸出結果如下,

1
2
3
false
true
n1=10,n2=10,n3=10

由此可見 std::ref() 回傳後的 std::reference_wrapper::get() 就相當於使用 &,有興趣的人可以去看 std::reference_wrapper 的原始碼實作,

其他參考
ref - C++ Reference
http://www.cplusplus.com/reference/functional/ref/
std::ref, std::cref - cppreference.com
https://en.cppreference.com/w/cpp/utility/functional/ref
std::reference_wrapper - cppreference.com
https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
std::ref和std::cref使用_幻想之漁-CSDN博客
https://blog.csdn.net/lmb1612977696/article/details/81543802
為什麼C++11引入了std::ref? - jiayayao - 博客園
https://www.cnblogs.com/jiayayao/p/6527713.html
C++11 的 std::ref 用法 | 拾荒志
https://murphypei.github.io/blog/2019/04/cpp-std-ref
std::ref()和& - 简书
https://www.jianshu.com/p/277675675593
C++11中std::reference_wrapper的理解 - 简书
https://www.jianshu.com/p/060901307b68
什麼是 函數式編程 functional programming?
https://ithelp.ithome.com.tw/articles/10192916

其它相關文章推薦
C/C++ 新手入門教學懶人包
C/C++ 字串轉數字的4種方法
C++ virtual 的兩種用法
C/C++ 字串反轉 reverse
C/C++ call by value傳值, call by pointer傳址, call by reference傳參考 的差別
C++ 類別樣板 class template
std::sort 用法與範例
std::find 用法與範例
std::queue 用法與範例
std::map 用法與範例
std::deque 用法與範例
std::vector 用法與範例